Trong bối cảnh công nghệ âm nhạc phát triển mạnh mẽ, các nền tảng phát trực tuyến (streaming) như Spotify đã trở thành nguồn dữ liệu phong phú phản ánh thói quen nghe nhạc của hàng trăm triệu người dùng trên toàn cầu. Thông qua các đặc tính âm thanh (audio features) như danceability, energy, tempo, loudness, valence và mức độ phổ biến (popularity), dữ liệu Spotify cho phép chúng ta khám phá xu hướng âm nhạc, đặc điểm của các bài hát được ưa chuộng, cũng như sự thay đổi phong cách qua từng giai đoạn lịch sử.
Bộ dữ liệu Spotify Dataset 1921–2020 (160k+ Tracks) cung cấp thông tin chi tiết về hơn 160.000 bài hát phát hành trong gần một thế kỷ, được thu thập và tổng hợp từ nền tảng Spotify. Dữ liệu bao gồm các đặc điểm kỹ thuật của bài hát, thông tin nghệ sĩ, thời gian phát hành và các chỉ số âm thanh được tính toán bằng thuật toán phân tích của Spotify API.
Mục tiêu của phần phân tích này là: - Khám phá xu hướng thay đổi của âm nhạc từ năm 1921 đến năm 2020 thông qua các chỉ số như tempo, energy, danceability, valence và popularity. - Phân tích mối quan hệ giữa các đặc trưng âm thanh với độ phổ biến của bài hát (popularity), nhằm nhận diện những yếu tố ảnh hưởng đến sự thành công trong âm nhạc đại chúng. - Thực hiện thống kê mô tả và trực quan hóa dữ liệu để hiểu rõ hơn về cấu trúc, đặc điểm và xu hướng tổng thể trong tập dữ liệu.
Bộ dữ liệu Spotify 1921–2020 bao gồm 19 biến chính, được chia thành
hai nhóm:
(1) Các biến định tính mô tả thông tin nghệ sĩ, bài
hát, chế độ nhạc;
(2) Các biến định lượng phản ánh các đặc trưng âm thanh
và độ phổ biến của từng bài hát.
| Tên biến | Kiểu dữ liệu | Mô tả chi tiết |
|---|---|---|
| valence | Numeric (0–1) | Mức độ “tích cực” hoặc “vui tươi” của bài hát. Giá trị càng cao thể hiện bài hát mang cảm xúc tích cực, ngược lại càng thấp biểu thị nhạc buồn, u tối. |
| year | Integer | Năm phát hành bài hát, giúp phân tích xu hướng âm nhạc theo thời gian. |
| acousticness | Numeric (0–1) | Mức độ “mộc” của bài hát, phản ánh tỷ lệ nhạc cụ thật so với nhạc điện tử. Giá trị cao thể hiện bài hát mang tính acoustic cao. |
| artists | Character | Tên nghệ sĩ hoặc nhóm nhạc thể hiện bài hát. Một bài hát có thể có nhiều nghệ sĩ hợp tác. |
| danceability | Numeric (0–1) | Mức độ dễ nhảy, thể hiện khả năng người nghe có thể cảm nhận và di chuyển theo nhịp. Giá trị cao → dễ nhảy, nhịp rõ ràng. |
| duration_ms | Numeric | Thời lượng của bài hát tính bằng mili-giây (ms). Có thể chuyển đổi sang phút để dễ quan sát. |
| energy | Numeric (0–1) | Mức năng lượng cảm nhận trong bài hát, thể hiện sự mạnh mẽ, cường độ âm thanh, tốc độ và hoạt động. |
| explicit | Numeric (0 hoặc 1) | Biến nhị phân thể hiện nội dung của bài hát có mang yếu tố “nhạy
cảm” hay không. Cụ thể, giá trị 1 biểu thị bài hát có chứa
ngôn từ hoặc nội dung không phù hợp với mọi đối tượng (explicit
content), còn giá trị 0 nghĩa là bài hát có nội dung bình
thường. Biến này giúp phân loại và phân tích tỷ lệ các bài hát
“explicit” theo từng năm hoặc thể loại. |
| id | Character | Mã định danh duy nhất của từng bài hát trên nền tảng Spotify. |
| instrumentalness | Numeric (0–1) | Xác suất bài hát không có lời. Giá trị gần 1 thể hiện bài hát hoàn toàn là nhạc cụ. |
| key | Factor / Integer (0–11) | Cao độ chính của bài hát, mã hóa theo 12 nốt nhạc trong thang âm phương Tây (C, C#, D, E♭, …, B). |
| liveness | Numeric (0–1) | Mức độ “trực tiếp” của bài hát – giá trị cao thể hiện bài hát có khả năng được thu âm trực tiếp (live). |
| loudness | Numeric (dB) | Độ lớn trung bình của bài hát, đo bằng đơn vị decibel (dB). Giá trị âm (vì dB đo so với mức 0 chuẩn). |
| mode | Factor (0 hoặc 1) | Chế độ của thang âm: 1 = trưởng (Major), 0
= thứ (Minor). Thường liên quan đến cảm xúc vui/buồn của bài hát. |
| name | Character | Tên của bài hát. |
| popularity | Numeric (0–100) | Mức độ phổ biến của bài hát trên Spotify. Giá trị càng cao thể hiện số lượt nghe và yêu thích càng lớn. |
| release_date | Date | Năm phát hành của bài hát. |
| speechiness | Numeric (0–1) | Mức độ “lời nói” trong bài hát. Giá trị cao thể hiện bài hát chứa nhiều đoạn nói, rap hoặc ít giai điệu. |
| tempo | Numeric (BPM) | Tốc độ nhịp của bài hát, đo bằng beats per minute (BPM). Giá trị cao → nhạc nhanh, sôi động; thấp → nhạc chậm, nhẹ. |
Nhìn chung, các biến định lượng trong bộ dữ liệu có giá trị trong
khoảng từ 0 đến 1 (chuẩn hóa theo hệ thống phân tích
của Spotify), ngoại trừ các biến đặc biệt như tempo (đơn vị
BPM), duration_ms (thời gian), và loudness
(đơn vị dB).
Các biến định tính như artists, name,
mode, explicit, key giúp nhận
diện và phân loại bài hát, hỗ trợ cho các phân tích thống kê và trực
quan hóa ở các phần tiếp theo.
# Nhập dữ liệu
library(data.table)
data <- fread("C:/Users/Administrator/Downloads/data.csv", encoding = "UTF-8")
# Gọi các thư viện cần thiết
library(dplyr)##
## Attaching package: 'dplyr'
## The following objects are masked from 'package:data.table':
##
## between, first, last
## The following objects are masked from 'package:stats':
##
## filter, lag
## The following objects are masked from 'package:base':
##
## intersect, setdiff, setequal, union
library(readr)
library(skimr)
library(knitr)
# 1.1.2.1: Kiểm tra kích thước dữ liệu Spotify
num_obs_data <- nrow(data) # Số lượng quan sát (bài hát)
num_vars_data <- ncol(data) # Số lượng biến (cột)
# In kết quả kích thước
cat("Số lượng quan sát (bài hát):", format(num_obs_data, big.mark = ","), "\n")Số lượng quan sát (bài hát): 170,653
Số lượng biến: 19
Dữ liệu gốc bao gồm 19 biến với 170.653 quan sát.
Cấu trúc dữ liệu (str):
Classes ‘data.table’ and ‘data.frame’: 170653 obs. of 19 variables: $
valence : num 0.0594 0.963 0.0394 0.165 0.253 0.196 0.406 0.0731 0.721
0.771 … $ year : int 1921 1921 1921 1921 1921 1921 1921 1921 1921 1921 …
$ acousticness : num 0.982 0.732 0.961 0.967 0.957 0.579 0.996 0.993
0.996 0.982 … $ artists : chr “[‘Sergei Rachmaninoff’, ‘James Levine’,
‘Berliner Philharmoniker’]” “[‘Dennis Day’]” “[‘KHP Kridhamardawa
Karaton Ngayogyakarta Hadiningrat’]” “[‘Frank Parker’]” … $ danceability
: num 0.279 0.819 0.328 0.275 0.418 0.697 0.518 0.389 0.485 0.684 … $
duration_ms : int 831667 180533 500062 210000 166693 395076 159507
218773 161520 196560 … $ energy : num 0.211 0.341 0.166 0.309 0.193
0.346 0.203 0.088 0.13 0.257 … $ explicit : int 0 0 0 0 0 0 0 0 0 0 … $
id : chr “4BJqT0PrAfrxzMOxytFOIz” “7xPhfUan2yNtyFG0cUWkt8”
“1o6I8BglA6ylDMrIELygv1” “3ftBPsC5vPBKxYSee08FDH” … $ instrumentalness:
num 8.78e-01 0.00 9.13e-01 2.77e-05 1.68e-06 1.68e-01 0.00 5.27e-01
1.51e-01 0.00 … $ key : int 10 7 3 5 3 2 0 1 5 8 … $ liveness : num
0.665 0.16 0.101 0.381 0.229 0.13 0.115 0.363 0.104 0.504 … $ loudness :
num -20.1 -12.44 -14.85 -9.32 -10.1 … $ mode : int 1 1 1 1 1 1 1 0 0 1 …
$ name : chr “Piano Concerto No. 3 in D Minor, Op. 30: III. Finale. Alla
breve” “Clancy Lowered the Boom” “Gati Bali” “Danny Boy” … $ popularity
: int 4 5 5 3 2 6 4 2 0 0 … $ release_date : chr “1921” “1921” “1921”
“1921” … $ speechiness : num 0.0366 0.415 0.0339 0.0354 0.038 0.07
0.0615 0.0456 0.0483 0.399 … $ tempo : num 81 60.9 110.3 100.1 101.7 … -
attr(*, “.internal.selfref”)=
# 2. Hiển thị 6 dòng đầu tiên của bộ dữ liệu với các cột tiêu biểu
cat("\n6 dòng đầu tiên của dữ liệu Spotify:\n")6 dòng đầu tiên của dữ liệu Spotify:
# Chọn một số cột tiêu biểu để hiển thị (đại diện cho cả nhóm định tính và định lượng)
selected_cols_for_head <- c("artists", "name", "year", "popularity",
"danceability", "energy", "tempo", "valence")
# Kiểm tra xem các cột này có tồn tại trong dữ liệu không
existing_selected_cols <- selected_cols_for_head[selected_cols_for_head %in% colnames(data)]
# Hiển thị bảng 6 dòng đầu tiên với các cột tiêu biểu
kable(
head(data %>% select(all_of(existing_selected_cols))),
caption = "Bảng 1.1.2: Sáu quan sát đầu tiên trong bộ dữ liệu Spotify (các cột tiêu biểu)",
format = "pipe",
align = "l"
)| artists | name | year | popularity | danceability | energy | tempo | valence |
|---|---|---|---|---|---|---|---|
| [‘Sergei Rachmaninoff’, ‘James Levine’, ‘Berliner Philharmoniker’] | Piano Concerto No. 3 in D Minor, Op. 30: III. Finale. Alla breve | 1921 | 4 | 0.279 | 0.211 | 80.954 | 0.0594 |
| [‘Dennis Day’] | Clancy Lowered the Boom | 1921 | 5 | 0.819 | 0.341 | 60.936 | 0.9630 |
| [‘KHP Kridhamardawa Karaton Ngayogyakarta Hadiningrat’] | Gati Bali | 1921 | 5 | 0.328 | 0.166 | 110.339 | 0.0394 |
| [‘Frank Parker’] | Danny Boy | 1921 | 3 | 0.275 | 0.309 | 100.109 | 0.1650 |
| [‘Phil Regan’] | When Irish Eyes Are Smiling | 1921 | 2 | 0.418 | 0.193 | 101.665 | 0.2530 |
| [‘KHP Kridhamardawa Karaton Ngayogyakarta Hadiningrat’] | Gati Mardika | 1921 | 6 | 0.697 | 0.346 | 119.824 | 0.1960 |
Dựa trên kết quả kiểm tra cấu trúc dữ liệu bằng hàm
str(), hầu hết các biến trong bộ dữ liệu Spotify đều có
kiểu dữ liệu phù hợp.
Tuy nhiên, có ba biến cần được chuyển đổi để thuận tiện
cho việc xử lý và phân tích thống kê:
explicit: hiện ở dạng số nguyên (0/1) → nên chuyển
thành factor với nhãn “Không nhạy cảm” và “Nhạy
cảm”.mode: hiện ở dạng số nguyên (0/1) → nên chuyển thành
factor với nhãn “Minor” và “Major”.release_date: có giá trị không đồng nhất (có dòng chỉ
chứa năm, có dòng đầy đủ ngày/tháng/năm), nên cần tách phần năm (4 ký tự
đầu tiên) và chuyển thành số nguyên (integer).Việc chuyển đổi này giúp đảm bảo tính chính xác và rõ ràng hơn trong các thao tác thống kê, mô hình hóa và trực quan hóa dữ liệu.
data <- data %>%
mutate(
explicit = factor(explicit,
levels = c(0, 1),
labels = c("Không nhạy cảm", "Nhạy cảm")),
mode = factor(mode,
levels = c(0, 1),
labels = c("Minor", "Major")),
# Tách 4 ký tự đầu tiên của release_date để lấy phần năm
release_year = as.integer(substr(release_date, 1, 4))
)
# Kiểm tra lại cấu trúc dữ liệu sau khi chuyển đổi
str(data)Classes ‘data.table’ and ‘data.frame’: 170653 obs. of 20 variables: $
valence : num 0.0594 0.963 0.0394 0.165 0.253 0.196 0.406 0.0731 0.721
0.771 … $ year : int 1921 1921 1921 1921 1921 1921 1921 1921 1921 1921 …
$ acousticness : num 0.982 0.732 0.961 0.967 0.957 0.579 0.996 0.993
0.996 0.982 … $ artists : chr “[‘Sergei Rachmaninoff’, ‘James Levine’,
‘Berliner Philharmoniker’]” “[‘Dennis Day’]” “[‘KHP Kridhamardawa
Karaton Ngayogyakarta Hadiningrat’]” “[‘Frank Parker’]” … $ danceability
: num 0.279 0.819 0.328 0.275 0.418 0.697 0.518 0.389 0.485 0.684 … $
duration_ms : int 831667 180533 500062 210000 166693 395076 159507
218773 161520 196560 … $ energy : num 0.211 0.341 0.166 0.309 0.193
0.346 0.203 0.088 0.13 0.257 … $ explicit : Factor w/ 2 levels “Không
nhạy cảm”,..: 1 1 1 1 1 1 1 1 1 1 … $ id : chr “4BJqT0PrAfrxzMOxytFOIz”
“7xPhfUan2yNtyFG0cUWkt8” “1o6I8BglA6ylDMrIELygv1”
“3ftBPsC5vPBKxYSee08FDH” … $ instrumentalness: num 8.78e-01 0.00
9.13e-01 2.77e-05 1.68e-06 1.68e-01 0.00 5.27e-01 1.51e-01 0.00 … $ key
: int 10 7 3 5 3 2 0 1 5 8 … $ liveness : num 0.665 0.16 0.101 0.381
0.229 0.13 0.115 0.363 0.104 0.504 … $ loudness : num -20.1 -12.44
-14.85 -9.32 -10.1 … $ mode : Factor w/ 2 levels “Minor”,“Major”: 2 2 2
2 2 2 2 1 1 2 … $ name : chr “Piano Concerto No. 3 in D Minor, Op. 30:
III. Finale. Alla breve” “Clancy Lowered the Boom” “Gati Bali” “Danny
Boy” … $ popularity : int 4 5 5 3 2 6 4 2 0 0 … $ release_date : chr
“1921” “1921” “1921” “1921” … $ speechiness : num 0.0366 0.415 0.0339
0.0354 0.038 0.07 0.0615 0.0456 0.0483 0.399 … $ tempo : num 81 60.9
110.3 100.1 101.7 … $ release_year : int 1921 1921 1921 1921 1921 1921
1921 1921 1921 1921 … - attr(*, “.internal.selfref”)=
Sau khi thực hiện chuyển đổi, các biến explicit và mode đã được định dạng lại thành biến định tính với nhãn rõ ràng, giúp dễ nhận diện nội dung bài hát và tính chất âm nhạc. Biến release_date được tách năm và chuyển thành release_year (kiểu số nguyên), giúp thuận tiện cho việc phân tích xu hướng âm nhạc theo thời gian. Bộ dữ liệu sau khi xử lý bao gồm 20 biến và 170,653 quan sát, sẵn sàng cho bước kiểm tra giá trị thiếu và thống kê mô tả.
Mục đích:
Bước này nhằm tóm tắt nhanh các đặc điểm thống kê cơ bản của toàn bộ bộ
dữ liệu Spotify, bao gồm: - Kiểm tra số lượng giá trị hợp lệ và giá trị
thiếu (NA).
- Quan sát phạm vi giá trị (min, max) và độ phân tán của các biến định
lượng.
- Xem xét số lượng mức (levels) của các biến định tính.
Việc kiểm tra này giúp nhận diện sớm các vấn đề về dữ liệu trước khi
tiến hành thống kê mô tả và trực quan hóa.
library(skimr)
# Tùy chỉnh skimr để bỏ biểu đồ nhỏ trong bảng tóm tắt
my_skim <- skim_with(
numeric = list(hist = NULL), # Bỏ minicell histogram để hiển thị gọn
append = FALSE
)## Creating new skimming functions for the following classes: hist.
## They did not have recognized defaults. Call get_default_skimmers() for more information.
# Áp dụng skim cho bộ dữ liệu Spotify
skim_summary_custom <- my_skim(data)
# In ra kết quả tóm tắt
print(skim_summary_custom)── Data Summary ──────────────────────── Values Name data
Number of rows 170653 Number of columns 20
Key NULL
_______________________
Column type frequency:
character 4
factor 2
numeric 14
________________________
Group variables None
── Variable type: character ──────────────────────────────────────────────────── skim_variable n_missing complete_rate min max empty n_unique whitespace 1 artists 0 1 5 663 0 34088 0 2 id 0 1 22 22 0 170653 0 3 name 0 1 1 203 0 133638 0 4 release_date 0 1 4 10 0 11244 0
── Variable type: factor
─────────────────────────────────────────────────────── skim_variable
n_missing complete_rate ordered n_unique top_counts
1 explicit 0 1 FALSE 2 Khô: 156220, Nhạ: 14433 2 mode 0 1 FALSE 2 Maj:
120635, Min: 50018
── Variable type: numeric ────────────────────────────────────────────────────── skim_variable n_missing complete_rate mean sd p0 1 valence 0 1 0.529 0.263 0 2 year 0 1 1977. 25.9 1921 3 acousticness 0 1 0.502 0.376 0 4 danceability 0 1 0.537 0.176 0 5 duration_ms 0 1 230948. 126118. 5108 6 energy 0 1 0.482 0.268 0 7 instrumentalness 0 1 0.167 0.313 0 8 key 0 1 5.20 3.52 0 9 liveness 0 1 0.206 0.175 0 10 loudness 0 1 -11.5 5.70 -60 11 popularity 0 1 31.4 21.8 0 12 speechiness 0 1 0.0984 0.163 0 13 tempo 0 1 117. 30.7 0 14 release_year 0 1 1977. 25.9 1921 p25 p50 p75 p100 hist 1 0.317 0.54 0.747 1 ▅▇▇▇▆ 2 1956 1977 1999 2020 ▃▇▇▇▇ 3 0.102 0.516 0.893 0.996 ▇▃▂▃▇ 4 0.415 0.548 0.668 0.988 ▁▅▇▇▂ 5 169827 207467 262400 5403500 ▇▁▁▁▁ 6 0.255 0.471 0.703 1 ▆▇▇▇▅ 7 0 0.000216 0.102 1 ▇▁▁▁▁ 8 2 5 8 11 ▇▃▃▅▆ 9 0.0988 0.136 0.261 1 ▇▃▁▁▁ 10 -14.6 -10.6 -7.18 3.86 ▁▁▁▇▆ 11 11 33 48 100 ▇▇▇▂▁ 12 0.0349 0.045 0.0756 0.97 ▇▁▁▁▁ 13 93.4 115. 136. 244. ▁▅▇▂▁ 14 1956 1977 1999 2020 ▃▇▇▇▇
Nhận xét:
Kết quả tóm tắt từ hàm skim() cho thấy bức tranh tổng quan
khá rõ ràng về bộ dữ liệu Spotify, với quy mô lớn (170.653 dòng
và 20 biến), phản ánh dữ liệu được thu thập tốt và đồng
nhất.
Về cấu trúc và kiểu dữ liệu:
Bộ dữ liệu gồm chủ yếu là biến định lượng (14 biến),
bên cạnh 4 biến ký tự và 2 biến phân
loại. Cấu trúc này rất phù hợp cho việc khai thác xu hướng, mối
quan hệ giữa các đặc trưng âm nhạc và độ phổ biến của bài hát.
Về tính đầy đủ của dữ liệu:
Tất cả các biến đều có complete_rate = 1, tức là
không có giá trị thiếu (NA). Điều này cho thấy dữ liệu
gốc được xử lý sạch sẽ, không cần thao tác điền hoặc loại bỏ giá trị
khuyết.
Phân tích sơ bộ nhóm biến định lượng:
Các đặc trưng âm nhạc như valence, energy,
danceability, acousticness có giá trị dao động
trong khoảng 0–1, đúng với thang đo kỹ thuật của Spotify.
Biến popularity có trung bình khoảng 31
điểm và độ lệch chuẩn lớn (21,8), thể hiện mức
độ phổ biến giữa các bài hát rất khác nhau.
Các biến year và release_year trải dài từ
1921 đến 2020, cho phép khai thác sự thay đổi xu hướng
âm nhạc qua gần một thế kỷ.
Phân tích sơ bộ nhóm biến định tính:
Biến explicit cho thấy phần lớn bài hát không chứa
nội dung nhạy cảm (≈91%), chỉ có khoảng 9%
mang nội dung “Explicit”.
Biến mode cũng thể hiện sự chênh lệch tương tự, với
70% bài hát ở điệu trưởng (Major) và 30% ở điệu
thứ (Minor), cho thấy xu hướng âm nhạc thiên về giai điệu tươi
sáng.
Tổng kết:
Dữ liệu có chất lượng tốt, không khuyết giá trị và các biến được ghi
nhận đúng định dạng. Đây là nền tảng thuận lợi để tiến hành thống kê mô
tả chi tiết và trực quan hóa trong các bước kế tiếp.
Kiểm tra giá trị khuyết thiếu (NA):
# Tính tổng số giá trị NA trong mỗi biến
na_counts <- colSums(is.na(data))
# Lọc ra các biến có ít nhất 1 giá trị NA
cols_with_na <- na_counts[na_counts > 0]
# Kiểm tra và in kết quả
if (length(cols_with_na) == 0) {
cat("Bộ dữ liệu Spotify không có giá trị thiếu (NA) trong bất kỳ biến nào.\n")
} else {
cat("Các biến có chứa giá trị NA và số lượng tương ứng:\n")
print(cols_with_na)
}Bộ dữ liệu Spotify không có giá trị thiếu (NA) trong bất kỳ biến nào.
Kiểm tra bản ghi trùng lặp:
# Xác định các dòng trùng lặp trong bộ dữ liệu
duplicate_rows <- data[duplicated(data), ]
# Kiểm tra số lượng bản ghi trùng lặp
num_duplicates <- nrow(duplicate_rows)
# In kết quả
if (num_duplicates == 0) {
cat("Không có bản ghi trùng lặp trong bộ dữ liệu Spotify.\n")
} else {
cat("Phát hiện", num_duplicates, "bản ghi trùng lặp trong bộ dữ liệu.\n")
}Không có bản ghi trùng lặp trong bộ dữ liệu Spotify.
Chuẩn hóa tên biến trong bộ dữ liệu:
##
## Attaching package: 'janitor'
## The following objects are masked from 'package:stats':
##
## chisq.test, fisher.test
data <- data %>%
janitor::clean_names()
# Kiểm tra lại danh sách tên biến sau khi chuẩn hóa
names(data)[1] “valence” “year” “acousticness” “artists”
[5] “danceability” “duration_ms” “energy” “explicit”
[9] “id” “instrumentalness” “key” “liveness”
[13] “loudness” “mode” “name” “popularity”
[17] “release_date” “speechiness” “tempo” “release_year”
#1 Chuẩn hóa biến năm phát hành (release_year)
data <- data %>%
mutate(
release_year = ifelse(is.na(release_year), year, release_year)
)
cat("Hoàn tất chuẩn hóa biến năm phát hành (release_year).\n")Hoàn tất chuẩn hóa biến năm phát hành (release_year).
#2 Tạo biến thập kỷ phát hành (Decade)
data <- data %>%
mutate(
Decade = paste0(floor(release_year / 10) * 10, "s")
)
cat("Đã tạo biến thập kỷ (Decade) từ release_year.\n")Đã tạo biến thập kỷ (Decade) từ release_year.
#3 Phân loại bài hát theo giai đoạn phát triển âm nhạc (Era)
data <- data %>%
mutate(
Era = case_when(
release_year < 1980 ~ "Cổ điển (Classic Era)",
release_year >= 1980 & release_year < 2000 ~ "Hiện đại (Modern Era)",
release_year >= 2000 ~ "Kỹ thuật số (Digital Era)",
TRUE ~ NA_character_
)
)
cat("Đã tạo biến phân loại giai đoạn âm nhạc (Era).\n")Đã tạo biến phân loại giai đoạn âm nhạc (Era).
#4 Kiểm tra kết quả sau khi xử lý thời gian
cat("\nKiểm tra phân bố số lượng bài hát theo giai đoạn âm nhạc:\n")Kiểm tra phân bố số lượng bài hát theo giai đoạn âm nhạc:
Cổ điển (Classic Era) Hiện đại (Modern Era) Kỹ thuật số (Digital Era)
89452 39751 41450
Kiểm tra phân bố số lượng bài hát theo thập kỷ phát hành:
1920s 1930s 1940s 1950s 1960s 1970s 1980s 1990s 2000s 2010s 2020s 5126 9549 15378 19850 19549 20000 19850 19901 19646 19774 2030
Trong bộ dữ liệu Spotify, các biến ban đầu chủ yếu mô tả đặc điểm kỹ thuật của bài hát như năng lượng, khả năng nhảy, độ phổ biến, nhịp độ, thời lượng… Để phục vụ cho các phân tích sâu hơn ở phần sau (như xu hướng âm nhạc theo thời gian, đặc điểm bài hát phổ biến, mối liên hệ giữa năng lượng và độ phổ biến), ta tiến hành tạo thêm một số biến mới có ý nghĩa thực tiễn trong nghiên cứu âm nhạc.
library(tibble)
library(knitr)
# Tạo bảng mô tả các biến mới
var_desc <- tribble(
~Biến_mới, ~Cách_tạo, ~Ý_nghĩa_thực_tiễn,
"duration_min", "duration_ms / 60000",
"Thời lượng bài hát (phút) — phản ánh xu hướng sáng tác (bài ngắn thường phổ biến hơn).",
"tempo_category", "Phân loại tempo: Chậm / Trung bình / Nhanh",
"Giúp xem nhịp độ bài hát phổ biến theo từng giai đoạn hoặc thể loại.",
"is_explicit_factor", "Phân loại explicit thành Có lời nhạy cảm / Không nhạy cảm",
"Phản ánh mức độ thay đổi trong nội dung âm nhạc hiện đại.",
"popularity_level", "Nhóm popularity thành Thấp / Trung bình / Cao",
"Cho phép so sánh đặc điểm kỹ thuật giữa các bài hát có độ phổ biến khác nhau.",
"energy_dance_ratio", "energy / danceability",
"Đo sự cân bằng giữa năng lượng và khả năng khiêu vũ — chỉ báo mức độ sôi động.",
"release_decade", "floor(release_year / 10) * 10",
"Biến thập kỷ phát hành, dùng để phân tích xu hướng âm nhạc theo thời gian."
)
kable(var_desc,
caption = "Bảng 1.1.11: Mô tả các biến mới được tạo trong bộ dữ liệu Spotify",
format = "pipe",
align = "l")| Biến_mới | Cách_tạo | Ý_nghĩa_thực_tiễn |
|---|---|---|
| duration_min | duration_ms / 60000 | Thời lượng bài hát (phút) — phản ánh xu hướng sáng tác (bài ngắn thường phổ biến hơn). |
| tempo_category | Phân loại tempo: Chậm / Trung bình / Nhanh | Giúp xem nhịp độ bài hát phổ biến theo từng giai đoạn hoặc thể loại. |
| is_explicit_factor | Phân loại explicit thành Có lời nhạy cảm / Không nhạy cảm | Phản ánh mức độ thay đổi trong nội dung âm nhạc hiện đại. |
| popularity_level | Nhóm popularity thành Thấp / Trung bình / Cao | Cho phép so sánh đặc điểm kỹ thuật giữa các bài hát có độ phổ biến khác nhau. |
| energy_dance_ratio | energy / danceability | Đo sự cân bằng giữa năng lượng và khả năng khiêu vũ — chỉ báo mức độ sôi động. |
| release_decade | floor(release_year / 10) * 10 | Biến thập kỷ phát hành, dùng để phân tích xu hướng âm nhạc theo thời gian. |
# TẠO CÁC BIẾN MỚI CÓ Ý NGHĨA THỰC TIỄN
data <- data %>%
mutate(
# 1 Thời lượng bài hát (phút)
duration_min = duration_ms / 60000,
# 2 Phân loại nhịp độ
tempo_category = case_when(
tempo < 90 ~ "Chậm",
tempo >= 90 & tempo < 130 ~ "Trung bình",
tempo >= 130 ~ "Nhanh"
),
# 3 Phân loại nội dung nhạy cảm
is_explicit_factor = ifelse(explicit == "Nhạy cảm", "Có lời nhạy cảm", "Không nhạy cảm"),
# 4 Phân loại mức độ phổ biến
popularity_level = case_when(
popularity < 30 ~ "Thấp",
popularity >= 30 & popularity < 60 ~ "Trung bình",
popularity >= 60 ~ "Cao"
),
# 5 Tỷ lệ năng lượng / khả năng nhảy
energy_dance_ratio = energy / danceability,
# 6 Thập kỷ phát hành
release_decade = floor(release_year / 10) * 10
)
# Xem trước các biến mới
kable(head(data %>% select(name, duration_min, tempo_category, is_explicit_factor,
popularity_level, energy_dance_ratio, release_decade)),
caption = "Bảng 1.1.11: Các biến mới có ý nghĩa thực tiễn trong bộ dữ liệu Spotify",
format = "pipe")| name | duration_min | tempo_category | is_explicit_factor | popularity_level | energy_dance_ratio | release_decade |
|---|---|---|---|---|---|---|
| Piano Concerto No. 3 in D Minor, Op. 30: III. Finale. Alla breve | 13.861117 | Chậm | Không nhạy cảm | Thấp | 0.7562724 | 1920 |
| Clancy Lowered the Boom | 3.008883 | Chậm | Không nhạy cảm | Thấp | 0.4163614 | 1920 |
| Gati Bali | 8.334367 | Trung bình | Không nhạy cảm | Thấp | 0.5060976 | 1920 |
| Danny Boy | 3.500000 | Trung bình | Không nhạy cảm | Thấp | 1.1236364 | 1920 |
| When Irish Eyes Are Smiling | 2.778217 | Trung bình | Không nhạy cảm | Thấp | 0.4617225 | 1920 |
| Gati Mardika | 6.584600 | Trung bình | Không nhạy cảm | Thấp | 0.4964132 | 1920 |
Bảng trên minh họa một số biến mới được tạo ra từ dữ liệu gốc. Kết quả cho thấy một số bài hát cổ điển có thời lượng dài hơn trung bình (trên 10 phút), trong khi hầu hết bài hát khác chỉ kéo dài từ 2 đến 5 phút. Các biến phân loại như tempo_category và popularity_level giúp mô tả rõ ràng hơn đặc điểm âm nhạc của từng giai đoạn. Biến energy_dance_ratio phản ánh mức độ sôi động của bài hát, với giá trị cao thường gặp ở những bản nhạc có năng lượng mạnh nhưng không dễ nhảy. Việc chuẩn hóa các biến này giúp thuận tiện cho các phân tích và trực quan hóa xu hướng âm nhạc ở các phần sau.
# 1 Nhóm biến chung (xuất hiện trong cả hai bộ)
common_vars <- c("name", "artists", "release_year", "release_decade")
# 2 Nhóm 1: Đặc điểm kỹ thuật âm nhạc
group1_vars <- c(common_vars,
"danceability", "energy", "valence", "acousticness", "instrumentalness",
"liveness", "loudness", "speechiness", "tempo", "key", "mode",
"energy_dance_ratio")
group1_vars <- unique(group1_vars)
# 3 Nhóm 2: Độ phổ biến & thông tin tổng quan
group2_vars <- c(common_vars,
"duration_min", "explicit", "popularity", "popularity_level",
"is_explicit_factor", "tempo_category", "release_date")
group2_vars <- unique(group2_vars)
# 4 Tạo hai bộ dữ liệu con tương ứng
spotify_group1 <- data %>%
select(intersect(group1_vars, colnames(data)))
spotify_group2 <- data %>%
select(intersect(group2_vars, colnames(data)))
# 5 Kiểm tra nhanh kích thước và tên cột
cat("\nNhóm 1 (Đặc điểm kỹ thuật âm nhạc):\n")Nhóm 1 (Đặc điểm kỹ thuật âm nhạc):
Số biến: 16
[1] “name” “artists” “release_year”
[4] “release_decade” “danceability” “energy”
[7] “valence” “acousticness” “instrumentalness”
[10] “liveness” “loudness” “speechiness”
[13] “tempo” “key” “mode”
[16] “energy_dance_ratio”
Nhóm 2 (Độ phổ biến & Thông tin tổng quan):
Số biến: 11
[1] “name” “artists” “release_year”
[4] “release_decade” “duration_min” “explicit”
[7] “popularity” “popularity_level” “is_explicit_factor” [10]
“tempo_category” “release_date”
Trong mục này, bộ dữ liệu Spotify đã được chia thành hai nhóm hợp lý: nhóm đặc điểm kỹ thuật âm nhạc (bao gồm các chỉ số như danceability, energy, valence…) và nhóm độ phổ biến & thông tin tổng quan (bao gồm popularity, explicit, duration, tempo_category…). Các biến chung như tên bài hát, nghệ sĩ, năm phát hành được giữ trong cả hai nhóm để liên kết dữ liệu. Việc phân nhóm này giúp tổ chức dữ liệu khoa học, thuận tiện cho phân tích chuyên biệt, đồng thời lưu cả ba bộ dữ liệu ra spotify_groups.RData đảm bảo dễ dàng truy xuất và tái sử dụng cho các bước phân tích và trực quan hóa tiếp theo.
# 1. Chọn các biến định lượng thuộc Nhóm 1 (Đặc trưng kỹ thuật âm nhạc)
library(dplyr)
library(tidyr)
library(knitr)
vars_g1_numeric <- c("danceability", "energy", "valence", "acousticness",
"instrumentalness", "liveness", "loudness", "speechiness", "tempo",
"energy_dance_ratio")
# 2. Tính thống kê mô tả
summary_stats_g1_wide <- spotify_group1 %>%
select(any_of(vars_g1_numeric)) %>%
summarise(across(everything(),
list(Min = ~ min(.x, na.rm = TRUE),
Q1 = ~ quantile(.x, 0.25, na.rm = TRUE),
Median = ~ median(.x, na.rm = TRUE),
Mean = ~ mean(.x, na.rm = TRUE),
Q3 = ~ quantile(.x, 0.75, na.rm = TRUE),
Max = ~ max(.x, na.rm = TRUE)),
.names = "{.col}_{.fn}"))
# 3. Chuyển vị và định dạng bảng kết quả
summary_g1_final_table <- summary_stats_g1_wide %>%
as.matrix() %>% t() %>% as.data.frame() %>%
rownames_to_column("VarStat") %>%
mutate(
Statistic = factor(gsub(".*_", "", VarStat),
levels = c("Min","Q1","Median","Mean","Q3","Max")),
Variable = gsub("_.*", "", VarStat)
) %>%
rename(Value = V1) %>%
select(Variable, Statistic, Value) %>%
# Dùng mean để gộp các giá trị trùng (nếu có)
pivot_wider(names_from = Statistic, values_from = Value, values_fn = mean) %>%
mutate(across(where(is.numeric), ~ round(.x, 3))) %>%
arrange(Variable)
# 4. In bảng kết quả bằng kable
kable(summary_g1_final_table,
caption = "Bảng 1.3.1: Thống kê Mô tả Tổng quan (Đặc trưng kỹ thuật âm nhạc)",
col.names = c("Tên Biến", "Min", "Q1", "Median", "Mean", "Q3", "Max"),
format = "pipe",
align = "l")| Tên Biến | Min | Q1 | Median | Mean | Q3 | Max |
|---|---|---|---|---|---|---|
| acousticness | 0 | 0.102 | 0.516 | 0.502 | 0.893 | 0.996 |
| danceability | 0 | 0.415 | 0.548 | 0.537 | 0.668 | 0.988 |
| energy | 0 | 0.383 | 0.659 | Inf | 0.980 | Inf |
| instrumentalness | 0 | 0.000 | 0.000 | 0.167 | 0.102 | 1.000 |
| liveness | 0 | 0.099 | 0.136 | 0.206 | 0.261 | 1.000 |
| loudness | -60 | -14.615 | -10.580 | -11.468 | -7.183 | 3.855 |
| speechiness | 0 | 0.035 | 0.045 | 0.098 | 0.076 | 0.970 |
| tempo | 0 | 93.421 | 114.729 | 116.862 | 135.537 | 243.507 |
| valence | 0 | 0.317 | 0.540 | 0.529 | 0.747 | 1.000 |
Xu hướng trung tâm:
- Danceability: Median = 0.548, Mean = 0.537 → hơi lệch trái.
- Energy: Median = 0.659, Mean = Inf → có giá trị cực đoan ảnh hưởng đến
trung bình.
- Valence: Median = 0.540, Mean = 0.529 → gần đối xứng.
- Acousticness: Median = 0.516, Mean = 0.502 → gần đối xứng.
- Loudness: Median = -10.580, Mean = -11.468 → lệch trái.
- Tempo: Median = 114.729, Mean = 116.862 → lệch phải nhẹ.
Độ phân tán:
- Phạm vi min-max: acousticness (0–0.996), danceability (0–0.988),
energy (0–Inf), loudness (-60–3.855), tempo (0–243.507).
- Nhìn chung các biến biến động rộng, đặc biệt loudness và tempo, phản
ánh sự đa dạng lớn trong đặc trưng kỹ thuật âm nhạc.
Dấu hiệu độ lệch (Skewness):
- Lệch phải (Mean > Median): danceability, tempo.
- Gần đối xứng (Mean ≈ Median): valence, acousticness.
- Lệch trái (Mean < Median): loudness.
- Một số giá trị cực đoan, đặc biệt energy và loudness, làm trung bình
khác biệt so với Median.
Kết luận sơ bộ:
Các đặc trưng kỹ thuật âm nhạc cho thấy sự đa dạng đáng kể về nhịp điệu,
năng lượng, cảm xúc, âm lượng và tempo. Các giá trị cực đoan ở energy và
loudness cần lưu ý khi phân tích hoặc xây dựng mô hình tiếp theo.
# Tính SD cho các biến định lượng nhóm 1
sd_table_g1 <- spotify_group1 %>%
summarise(across(any_of(vars_g1_numeric), ~ sd(.x, na.rm = TRUE),
.names = "SD_{.col}")) %>%
pivot_longer(everything(), names_to = "Variable_Prefixed",
values_to = "Standard_Deviation") %>%
mutate(Variable = sub("^SD_", "", Variable_Prefixed)) %>%
select(Variable, Standard_Deviation) %>%
mutate(Standard_Deviation = round(Standard_Deviation, 3)) %>%
arrange(Variable)
# In bảng bằng kable
kable(sd_table_g1,
caption = "Bảng 1.3.2: Độ lệch chuẩn (Nhóm biến Đặc trưng kỹ thuật âm nhạc)",
col.names = c("Tên Biến", "Độ lệch chuẩn (SD)"),
format = "pipe", align = "l")| Tên Biến | Độ lệch chuẩn (SD) |
|---|---|
| acousticness | 0.376 |
| danceability | 0.176 |
| energy | 0.268 |
| energy_dance_ratio | NaN |
| instrumentalness | 0.313 |
| liveness | 0.175 |
| loudness | 5.698 |
| speechiness | 0.163 |
| tempo | 30.709 |
| valence | 0.263 |
Độ biến động về âm thanh acousticness SD = 0376 và instrumentalness SD = 0313 cho thấy mức độ đa dạng về đặc tính âm thanh và độ nhạc cụ trong các bản nhạc
Năng lượng và cảm xúc energy SD = 0268 và valence SD = 0263 phản ánh sự khác biệt về cảm xúc và năng lượng giữa các bài hát
Độ ổn định tương đối danceability SD = 0176, liveness SD = 0175, speechiness SD = 0163 cho thấy các bài hát có đặc trưng này phân bố tương đối tập trung quanh giá trị trung bình
Biến động lớn về nhịp điệu và âm lượng loudness SD = 5698 và tempo SD = 30709 biến động đáng kể phản ánh sự đa dạng về cường độ âm thanh và nhịp điệu giữa các bản nhạc
energy_dance_ratio có giá trị NaN cho thấy có thể tồn tại giá trị thiếu hoặc biến này cần được xử lý trước khi phân tích sâu hơn
Nhóm biến Đặc trưng kỹ thuật âm nhạc có mức độ phân tán khác
nhau
Một số biến như loudness, tempo, acousticness, instrumentalness biến
động lớn phản ánh sự đa dạng kỹ thuật âm nhạc
Trong khi các biến như danceability, liveness, speechiness tập trung
quanh giá trị trung bình biểu thị mức độ đồng đều tương đối giữa các bài
hát
##
## Attaching package: 'rlang'
## The following object is masked from 'package:data.table':
##
## :=
# Hàm tạo bảng tần suất theo biến phân loại
create_freq_table_vi <- function(data, var_name, caption_text) {
freq_table <- data %>%
count(!!rlang::sym(var_name), name = "SoLuong") %>%
mutate(TyLe_PhanTram = paste0(round(SoLuong / sum(SoLuong) * 100, 1), "%")) %>%
arrange(desc(SoLuong))
colnames(freq_table)[1] <- var_name
print(kable(freq_table,
caption = caption_text,
col.names = c(var_name, "Số lượng", "Tỷ lệ (%)"),
format = "pipe",
align = "l"))
}
# Tạo từng bảng riêng lẻ (mỗi lệnh = 1 bảng)
if ("release_decade" %in% names(spotify_group1)) {
create_freq_table_vi(spotify_group1, "release_decade",
"Bảng 1.3.1.3: Phân bố 'release_decade' (Nhóm biến Đặc trưng kỹ thuật âm nhạc)")
}| release_decade | Số lượng | Tỷ lệ (%) |
|---|---|---|
| 1970 | 20000 | 11.7% |
| 1990 | 19901 | 11.7% |
| 1950 | 19850 | 11.6% |
| 1980 | 19850 | 11.6% |
| 2010 | 19774 | 11.6% |
| 2000 | 19646 | 11.5% |
| 1960 | 19549 | 11.5% |
| 1940 | 15378 | 9% |
| 1930 | 9549 | 5.6% |
| 1920 | 5126 | 3% |
| 2020 | 2030 | 1.2% |
if ("explicit" %in% names(spotify_group1)) {
create_freq_table_vi(spotify_group1, "explicit",
"Bảng 1.3.1.4: Phân bố 'explicit' (Nhóm biến Đặc trưng kỹ thuật âm nhạc)")
}
if ("tempo_category" %in% names(spotify_group1)) {
create_freq_table_vi(spotify_group1, "tempo_category",
"Bảng 1.3.1.5: Phân bố 'tempo_category' (Nhóm biến Đặc trưng kỹ thuật âm nhạc)")
}
if ("popularity_level" %in% names(spotify_group1)) {
create_freq_table_vi(spotify_group1, "popularity_level",
"Bảng 1.3.1.6: Phân bố 'popularity_level' (Nhóm biến Đặc trưng kỹ thuật âm nhạc)")
}
if ("key" %in% names(spotify_group1)) {
create_freq_table_vi(spotify_group1, "key",
"Bảng 1.3.1.7: Phân bố 'key' (Nhóm biến Đặc trưng kỹ thuật âm nhạc)")
}| key | Số lượng | Tỷ lệ (%) |
|---|---|---|
| 0 | 21600 | 12.7% |
| 7 | 20803 | 12.2% |
| 2 | 18823 | 11% |
| 9 | 17571 | 10.3% |
| 5 | 16430 | 9.6% |
| 4 | 12933 | 7.6% |
| 1 | 12886 | 7.6% |
| 10 | 12148 | 7.1% |
| 8 | 10751 | 6.3% |
| 11 | 10670 | 6.3% |
| 6 | 8741 | 5.1% |
| 3 | 7297 | 4.3% |
if ("mode" %in% names(spotify_group1)) {
create_freq_table_vi(spotify_group1, "mode",
"Bảng 1.3.1.8: Phân bố 'mode' (Nhóm biến Đặc trưng kỹ thuật âm nhạc)")
}| mode | Số lượng | Tỷ lệ (%) |
|---|---|---|
| Major | 120635 | 70.7% |
| Minor | 50018 | 29.3% |
release_decade (Bảng 1.3.1.3) Phân bố khá đều theo từng thập kỷ từ 1920 → 2020. Các thập kỷ 1970 (11.7%), 1990 (11.7%), 1950 (11.6%), 1980 (11.6%) và 2010 (11.6%) chiếm tỷ trọng lớn nhất, phản ánh dataset tập trung nhiều vào âm nhạc từ giữa thế kỷ 20 đến đầu thế kỷ 21. Thập kỷ 2020 chiếm tỷ lệ thấp nhất (1.2%), do số lượng bài hát mới ít hoặc chưa được cập nhật đầy đủ.
key (Bảng 1.3.1.7) Các bản nhạc được phân bố theo 12 cung âm (0 → 11). Các cung phổ biến nhất là 0 (12.7%), 7 (12.2%) và 2 (11%), chiếm tỷ trọng cao, cho thấy các bản nhạc thường sử dụng cung dễ nghe và quen thuộc. Các cung ít phổ biến như 6 (5.1%) và 3 (4.3%) ít xuất hiện trong dataset.
mode (Bảng 1.3.1.8) Đa số các bản nhạc sử dụng Major (70.7%), trong khi Minor chỉ chiếm 29.3%. Điều này phản ánh xu hướng âm nhạc chủ đạo vui tươi, sáng sủa (Major), trong khi cảm xúc buồn, u sầu (Minor) ít xuất hiện hơn.
library(knitr)
vars_g1_quantile <- c("danceability", "energy", "valence", "acousticness",
"instrumentalness", "liveness", "loudness", "speechiness", "tempo")
quantile_results_g1 <- sapply(spotify_group1[, ..vars_g1_quantile],
quantile,
probs = c(0, 0.25, 0.5, 0.75, 1),
na.rm = TRUE)
rownames(quantile_results_g1) <- c("Min (0%)","Q1 (25%)","Median (50%)","Q3 (75%)","Max (100%)")
quantile_df_g1 <- as.data.frame(round(quantile_results_g1, 3)) %>%
rownames_to_column("Percentile")
kable(quantile_df_g1,
caption = "Bảng 1.3.8: Các điểm phân vị của các biến định lượng (Nhóm biến Đặc trưng kỹ thuật âm nhạc)",
format = "pipe", align = "l")| Percentile | danceability | energy | valence | acousticness | instrumentalness | liveness | loudness | speechiness | tempo |
|---|---|---|---|---|---|---|---|---|---|
| Min (0%) | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | 0.000 | -60.000 | 0.000 | 0.000 |
| Q1 (25%) | 0.415 | 0.255 | 0.317 | 0.102 | 0.000 | 0.099 | -14.615 | 0.035 | 93.421 |
| Median (50%) | 0.548 | 0.471 | 0.540 | 0.516 | 0.000 | 0.136 | -10.580 | 0.045 | 114.729 |
| Q3 (75%) | 0.668 | 0.703 | 0.747 | 0.893 | 0.102 | 0.261 | -7.183 | 0.076 | 135.537 |
| Max (100%) | 0.988 | 1.000 | 1.000 | 0.996 | 1.000 | 1.000 | 3.855 | 0.970 | 243.507 |
danceability = 0.548, energy = 0.471, valence = 0.540 cho thấy các bản nhạc có mức độ vừa phải về khả năng nhảy, năng lượng và cảm xúc.
acousticness = 0.516, liveness = 0.136, speechiness = 0.045 phản ánh phần lớn bài hát có âm thanh trực tiếp (live) và lời nói thấp, trong khi mức độ acoustic trung bình.
loudness dao động từ -60 đến 3.855, tempo từ 0 đến 243.507, thể hiện sự đa dạng lớn về cường độ âm thanh và nhịp điệu.
instrumentalness Max = 1 nhưng Median = 0, nghĩa là đa số bài hát không thuần instrumental, chỉ một số ít hoàn toàn nhạc cụ.
energy, valence, acousticness có Q1–Q3 khá rộng, phản ánh sự khác biệt đáng kể về năng lượng, cảm xúc và acoustic giữa các bản nhạc.
danceability, liveness, speechiness có Q1–Q3 hẹp hơn, phân bố tương đối tập trung quanh giá trị trung bình.
Nhóm biến đặc trưng kỹ thuật âm nhạc cho thấy sự đa dạng về nhịp điệu, âm lượng, năng lượng và acoustic, trong khi một số đặc tính như danceability, liveness, speechiness tập trung hơn. Giá trị cực đoan (Min/Max) chỉ ra một số bài hát đặc biệt khác biệt so với phần lớn mẫu.
Độ lệch (skewness) phản ánh mức độ không đối xứng của dữ liệu xung quanh giá trị trung bình. Cách hiểu:
Giá trị dương của độ lệch cho thấy phân phối kéo dài về phía các giá trị lớn (đuôi dài bên phải).
Giá trị âm của độ lệch cho thấy phân phối kéo dài về phía các giá trị nhỏ (đuôi dài bên trái).
Nếu độ lệch xấp xỉ 0, phân phối gần như đối xứng.
Độ lớn tuyệt đối của skewness càng cao, sự bất đối xứng càng rõ rệt.
library(dplyr)
library(knitr)
library(e1071)
# Danh sách các biến định lượng
vars_g1_shape <- c("danceability", "energy", "valence", "acousticness",
"instrumentalness", "liveness", "loudness", "speechiness", "tempo")
# Tính Skewness cho từng biến
skewness_table_g1 <- spotify_group1 %>%
select(any_of(vars_g1_shape)) %>%
summarise(across(everything(), ~ skewness(.x, na.rm = TRUE))) %>%
pivot_longer(everything(), names_to = "Variable", values_to = "Skewness") %>%
mutate(Skewness = round(Skewness, 3)) %>%
arrange(Variable)
# In bảng
kable(skewness_table_g1,
caption = "Bảng 1.3.10: Độ xiên (Skewness) của các biến định lượng (Nhóm biến Đặc trưng kỹ thuật âm nhạc)",
col.names = c("Tên Biến", "Độ xiên (Skewness)"),
format = "pipe", align = "l")| Tên Biến | Độ xiên (Skewness) |
|---|---|
| acousticness | -0.033 |
| danceability | -0.223 |
| energy | 0.112 |
| instrumentalness | 1.631 |
| liveness | 2.154 |
| loudness | -1.052 |
| speechiness | 4.048 |
| tempo | 0.450 |
| valence | -0.107 |
Các biến acousticness (-0.033), danceability (-0.223), energy (0.112) và valence (-0.107) có độ xiên gần 0, cho thấy phân phối gần đối xứng quanh giá trị trung bình. Điều này nghĩa là phần lớn bài hát tập trung quanh mức nhịp điệu, năng lượng và cảm xúc trung bình, không có nhiều giá trị cực đoan.
Biến tempo (0.450) lệch phải nhẹ, tức phần lớn bài hát có nhịp độ vừa phải, nhưng có một số bài hát với nhịp độ cao kéo dài đuôi phân phối. Ngược lại, loudness (-1.052) lệch trái, cho thấy đa số bài hát có âm lượng thấp, chỉ một số ít bài hát có âm lượng cao tạo đuôi dài về phía giá trị nhỏ.
Các biến instrumentalness (1.631), liveness (2.154) và speechiness (4.048) lệch phải đáng kể, phản ánh rằng phần lớn bài hát có giá trị thấp về nhạc cụ, độ sống động và speechiness, trong khi số ít bài hát có giá trị cao kéo dài đuôi phải. Đây là các giá trị cực đoan cần lưu ý vì có thể ảnh hưởng đến trung bình và kết quả phân tích.
Tóm lại, các đặc trưng cơ bản về cảm xúc và nhịp điệu gần đối xứng, còn các đặc trưng về nhạc cụ, độ sống động và speechiness có phân bố lệch phải đáng kể, cho thấy sự đa dạng kỹ thuật âm nhạc trong bộ dữ liệu.
Độ nhọn phản ánh mức độ tập trung của dữ liệu quanh trung tâm, đồng thời cho biết độ “dày” hay “mỏng” của phần đuôi so với phân phối chuẩn. Trong R, hàm kurtosis() thường trả giá trị Excess Kurtosis (kurtosis trừ đi 3).
Khi Excess Kurtosis = 0, phân phối có độ nhọn tương đương phân phối chuẩn (Mesokurtic).
Khi Excess Kurtosis > 0, dữ liệu có đỉnh nhọn hơn và đuôi dày hơn chuẩn (Leptokurtic), nghĩa là khả năng xuất hiện giá trị ngoại lai (outlier) cao hơn.
Khi Excess Kurtosis < 0, phân phối bằng phẳng hơn, đuôi mỏng hơn so với chuẩn (Platykurtic), tức dữ liệu phân tán đều hơn quanh trung tâm.
# Các biến định lượng nhóm 1 (Đặc trưng kỹ thuật âm nhạc)
vars_g1_shape <- c("danceability", "energy", "valence", "acousticness",
"instrumentalness", "liveness", "loudness", "speechiness", "tempo")
# Tính Excess Kurtosis
kurtosis_table_g1 <- sapply(spotify_group1[, ..vars_g1_shape],
kurtosis, na.rm = TRUE) %>%
round(3) %>% as.data.frame() %>%
rownames_to_column("Variable") %>%
rename(Excess_Kurtosis = ".") %>%
arrange(Variable)
# In bảng
kable(kurtosis_table_g1,
caption = "Bảng 1.3.11: Độ nhọn (Excess Kurtosis = Kurtosis − 3) của các biến định lượng (Nhóm biến Đặc trưng kỹ thuật âm nhạc)",
col.names = c("Tên Biến", "Độ nhọn (Kurtosis−3)"),
format = "pipe", align = "l")| Tên Biến | Độ nhọn (Kurtosis−3) |
|---|---|
| acousticness | -1.609 |
| danceability | -0.443 |
| energy | -1.100 |
| instrumentalness | 0.942 |
| liveness | 5.001 |
| loudness | 1.847 |
| speechiness | 17.000 |
| tempo | -0.078 |
| valence | -1.062 |
Các đặc trưng kỹ thuật âm nhạc trong dataset thể hiện sự phân bố khác nhau về độ lệch và độ nhọn. Danceability có độ xiên nhẹ trái (Skewness = -0.223) và độ nhọn hơi bẹt (Kurtosis = -0.443), cho thấy các giá trị hơi tập trung xung quanh trung tâm nhưng không quá cực đoan. Energy gần đối xứng (Skewness = 0.112) nhưng có độ nhọn thấp (Kurtosis = -1.100), nghĩa là phân phối rộng hơn phân phối chuẩn, ít bị ảnh hưởng bởi giá trị cực đoan.
Các biến instrumentalness (Skewness = 1.631, Kurtosis = 0.942) và liveness (Skewness = 2.154, Kurtosis = 5.001) lệch phải đáng kể và có đuôi dài, phản ánh rằng hầu hết bài hát có giá trị thấp nhưng một số bài có giá trị cao, tạo ra outlier. Speechiness nổi bật với Skewness = 4.048 và Kurtosis = 17.000, cho thấy dữ liệu cực kỳ lệch phải với các giá trị cực đoan rất cao, là những outlier có ảnh hưởng lớn đến trung bình và độ nhọn của phân phối.
Các biến như loudness (Skewness = -1.052, Kurtosis = 1.847) lệch trái, nghĩa là nhiều bài hát có âm lượng gần trung bình nhưng một số giá trị cực thấp làm “kéo” trung bình xuống. Tempo và valence gần đối xứng với Skewness lần lượt là 0.450 và -0.107, độ nhọn tương đối bình thường, phản ánh sự phân bố đều hơn quanh trung tâm.
Nhìn chung, nhóm biến đặc trưng kỹ thuật âm nhạc cho thấy sự đa dạng về nhịp điệu, cảm xúc, độ nhạc cụ và độ “sôi động” của bài hát. Một số biến như speechiness, liveness, instrumentalness có outlier rõ rệt, cần lưu ý khi phân tích sâu hoặc xây dựng mô hình dự báo.
Kiểm định Shapiro-Wilk được sử dụng để xác định xem dữ liệu có tuân theo phân phối chuẩn hay không. Giả thuyết gốc (H0) của kiểm định là dữ liệu có phân phối chuẩn. Khi giá trị p > 0.05, H0 không bị bác bỏ, tức dữ liệu có xu hướng phân phối chuẩn; ngược lại, p ≤ 0.05 cho thấy dữ liệu không tuân theo phân phối chuẩn. Tuy nhiên, kiểm định này rất nhạy cảm với kích thước mẫu lớn: khi số quan sát vượt quá khoảng vài nghìn, kiểm định dễ phát hiện sự khác biệt rất nhỏ với phân phối chuẩn và thường báo dữ liệu không chuẩn ngay cả khi phân phối gần chuẩn. Do đó, với bộ dữ liệu lớn, nên thực hiện kiểm định trên một mẫu con nhỏ để có đánh giá sơ bộ hợp lý, đồng thời kết hợp quan sát đồ thị (histogram, Q-Q plot) để kiểm tra trực quan.
##
## Attaching package: 'purrr'
## The following objects are masked from 'package:rlang':
##
## %@%, flatten, flatten_chr, flatten_dbl, flatten_int, flatten_lgl,
## flatten_raw, invoke, splice
## The following object is masked from 'package:data.table':
##
## transpose
library(dplyr)
library(knitr)
library(tibble)
library(stats) # Chứa shapiro.test
# 1. Hàm kiểm định Shapiro-Wilk trên mẫu <= 5000
shapiro_on_sample <- function(x, max_n = 5000) {
x <- na.omit(x)
n_use <- min(length(x), max_n)
if (n_use < 3) return(tibble(N = length(x), W = NA_real_, p_value = NA_real_))
x_sample <- sample(x, n_use)
res <- shapiro.test(x_sample)
tibble(N = n_use, W = unname(res$statistic), p_value = unname(res$p.value))
}
# 2. Các biến định lượng nhóm đặc trưng kỹ thuật âm nhạc
vars_shapiro <- c("danceability","energy","valence","acousticness",
"instrumentalness","liveness","loudness","speechiness","tempo")
# 3. Áp dụng hàm cho từng biến
shapiro_df <- map_dfr(vars_shapiro, ~ {
tmp <- spotify_group1[[.x]]
out <- shapiro_on_sample(tmp)
out$Variable <- .x
out
}) %>% relocate(Variable)
# 4. Định dạng bảng kết quả
shapiro_df <- shapiro_df %>%
mutate(p_value_fmt = format.pval(p_value, digits = 3, eps = 0.001),
Kết_luận = ifelse(!is.na(p_value) & p_value < 0.05,
"Bác bỏ phân phối chuẩn (p<0.05)",
"Không bác bỏ (p≥0.05)"))
# 5. In bảng bằng kable
kable(shapiro_df %>% select(Variable, N, W, p_value_fmt, Kết_luận),
caption = "Bảng: Kiểm định Shapiro–Wilk cho các đặc trưng kỹ thuật âm nhạc (N≤5000)",
col.names = c("Biến","Cỡ mẫu","W","P-value","Kết luận"),
format = "pipe", align = "l")| Biến | Cỡ mẫu | W | P-value | Kết luận |
|---|---|---|---|---|
| danceability | 5000 | 0.9922849 | <0.001 | Bác bỏ phân phối chuẩn (p<0.05) |
| energy | 5000 | 0.9637360 | <0.001 | Bác bỏ phân phối chuẩn (p<0.05) |
| valence | 5000 | 0.9674011 | <0.001 | Bác bỏ phân phối chuẩn (p<0.05) |
| acousticness | 5000 | 0.8693734 | <0.001 | Bác bỏ phân phối chuẩn (p<0.05) |
| instrumentalness | 5000 | 0.5780255 | <0.001 | Bác bỏ phân phối chuẩn (p<0.05) |
| liveness | 5000 | 0.7497968 | <0.001 | Bác bỏ phân phối chuẩn (p<0.05) |
| loudness | 5000 | 0.9434224 | <0.001 | Bác bỏ phân phối chuẩn (p<0.05) |
| speechiness | 5000 | 0.4342632 | <0.001 | Bác bỏ phân phối chuẩn (p<0.05) |
| tempo | 5000 | 0.9782116 | <0.001 | Bác bỏ phân phối chuẩn (p<0.05) |
Dựa trên kết quả kiểm định Shapiro–Wilk cho các biến định lượng nhóm đặc trưng kỹ thuật âm nhạc:
Các giá trị W đều thấp hơn 1, và p-value < 0.001 cho tất cả các biến, cho thấy rằng tất cả các biến đều không tuân theo phân phối chuẩn trên mẫu con 5000 quan sát.
Cụ thể:
Biến danceability (W = 0.993) và valence (W = 0.969) có giá trị W gần 1 hơn các biến khác, tức là phân phối của chúng tương đối gần với chuẩn, nhưng vẫn bị bác bỏ giả thuyết phân phối chuẩn do p-value rất nhỏ.
Các biến instrumentalness (W = 0.561), speechiness (W = 0.441) và liveness (W = 0.761) có giá trị W thấp hơn đáng kể, cho thấy phân phối bị lệch mạnh và có khả năng chứa nhiều giá trị ngoại lai (outlier).
Biến acousticness (W = 0.867), energy (W = 0.965), loudness (W = 0.945) và tempo (W = 0.979) cũng đều bị bác bỏ phân phối chuẩn, mặc dù mức độ lệch không mạnh bằng instrumentalness hay speechiness.
Tất cả các đặc trưng kỹ thuật âm nhạc đều phân phối không chuẩn, điều này cần được lưu ý khi áp dụng các phân tích thống kê yêu cầu giả định phân phối chuẩn, như hồi quy tuyến tính cổ điển. Trong thực hành, bạn có thể cân nhắc biến đổi dữ liệu (log, sqrt, Box-Cox) hoặc sử dụng các phương pháp phi tham số.
Phân tích trước đó về độ xiên (Skewness) và độ nhọn (Excess Kurtosis) trong Mục 1.3.1.5 đã cho thấy một số biến trong Nhóm 1 có phân phối lệch hoặc nhọn bất thường, cho thấy sự hiện diện của các giá trị cực đoan (outliers). Để hiểu rõ hơn về mức độ và tần suất của các giá trị này, chúng tôi sẽ áp dụng phương pháp IQR (Interquartile Range) để xác định các outlier. Phân tích này sẽ tập trung vào các biến có độ lệch cao nhất, giúp nhận diện các bản ghi đặc biệt, đánh giá tính đa dạng của dữ liệu âm nhạc và đảm bảo các bước phân tích thống kê tiếp theo không bị ảnh hưởng bởi những giá trị ngoại lai.
Trong phương pháp IQR, các giá trị được xem là ngoại lai nếu chúng nằm ngoài khoảng từ Q1 − 1,5 × IQR đến Q3 + 1,5 × IQR, trong đó Q1 và Q3 lần lượt là phần tử thứ nhất và thứ ba của tứ phân vị, còn IQR là khoảng tứ phân vị (Q3 − Q1).
# Hàm xác định outlier theo IQR
identify_outliers_iqr <- function(df, var) {
x <- df[[var]]
x <- na.omit(x) # loại NA
Q1 <- quantile(x, 0.25)
Q3 <- quantile(x, 0.75)
IQR_val <- Q3 - Q1
Lower_Bound <- Q1 - 1.5 * IQR_val
Upper_Bound <- Q3 + 1.5 * IQR_val
list(
var_name = var,
Q1 = Q1,
Q3 = Q3,
IQR = IQR_val,
Lower_Bound = Lower_Bound,
Upper_Bound = Upper_Bound
)
}
# Ví dụ áp dụng cho nhóm 1
vars_g1_outlier <- c("danceability", "energy", "valence", "acousticness",
"instrumentalness", "liveness", "loudness", "speechiness", "tempo")
outlier_results_list_g1 <- lapply(vars_g1_outlier, function(var) {
identify_outliers_iqr(spotify_group1, var)
})
outlier_threshold_df_g1 <- do.call(rbind, lapply(outlier_results_list_g1, function(res) {
data.frame(
Variable = res$var_name,
Q1 = round(res$Q1, 3), Q3 = round(res$Q3, 3), IQR = round(res$IQR, 3),
Lower_Bound = round(res$Lower_Bound, 3), Upper_Bound = round(res$Upper_Bound, 3)
)
}))
library(knitr)
kable(outlier_threshold_df_g1,
caption = "Bảng 1.3.12: Ngưỡng xác định giá trị ngoại lai theo phương pháp IQR (Nhóm Đặc trưng kỹ thuật âm nhạc)",
col.names = c("Tên Biến", "Q1", "Q3", "IQR", "Ngưỡng Dưới", "Ngưỡng Trên"),
format = "pipe", align = "l")| Tên Biến | Q1 | Q3 | IQR | Ngưỡng Dưới | Ngưỡng Trên | |
|---|---|---|---|---|---|---|
| 25% | danceability | 0.415 | 0.668 | 0.253 | 0.036 | 1.047 |
| 25%1 | energy | 0.255 | 0.703 | 0.448 | -0.417 | 1.375 |
| 25%2 | valence | 0.317 | 0.747 | 0.430 | -0.328 | 1.392 |
| 25%3 | acousticness | 0.102 | 0.893 | 0.791 | -1.084 | 2.080 |
| 25%4 | instrumentalness | 0.000 | 0.102 | 0.102 | -0.153 | 0.255 |
| 25%5 | liveness | 0.099 | 0.261 | 0.162 | -0.145 | 0.504 |
| 25%6 | loudness | -14.615 | -7.183 | 7.432 | -25.763 | 3.965 |
| 25%7 | speechiness | 0.035 | 0.076 | 0.041 | -0.026 | 0.137 |
| 25%8 | tempo | 93.421 | 135.537 | 42.116 | 30.247 | 198.711 |
Các kết quả bảng ngưỡng ngoại lai cho Nhóm biến đặc trưng kỹ thuật âm nhạc cho thấy sự phân bố dữ liệu và mức độ tiềm ẩn outlier ở từng biến:
Biến danceability có Q1 = 0.415, Q3 = 0.668, IQR = 0.253, với ngưỡng dưới 0.036 và ngưỡng trên 1.047. Điều này cho thấy dữ liệu tập trung trong khoảng trung bình, nhưng vẫn có thể có một số quan sát rất thấp hoặc cao hơn 1.047 được xem là ngoại lai.
Biến energy có IQR lớn hơn (0.448) và ngưỡng dưới -0.417, ngưỡng trên 1.375, phản ánh dữ liệu có phạm vi rộng hơn, khả năng xuất hiện các giá trị cực thấp hoặc cực cao cũng cao hơn so với danceability.
Valence có ngưỡng từ -0.328 đến 1.392, cho thấy phân bố cũng khá rộng, một số giá trị cực đoan có thể xuất hiện, nhưng tập trung vẫn chủ yếu trong khoảng trung tâm.
Biến acousticness có IQR = 0.791 và ngưỡng trên 2.080, ngưỡng dưới -1.084. Phạm vi rộng này phản ánh dữ liệu rất phân tán, đồng thời khả năng xuất hiện outlier cao, đặc biệt là các giá trị cực lớn.
Instrumentalness có IQR nhỏ (0.102), ngưỡng dưới -0.153 và ngưỡng trên 0.255, cho thấy hầu hết dữ liệu tập trung rất thấp, các giá trị vượt quá 0.255 có thể được coi là ngoại lai.
Biến liveness có ngưỡng trên 0.504, ngưỡng dưới -0.145, với IQR 0.162, dữ liệu tập trung chủ yếu quanh Q1-Q3 nhưng vẫn có khả năng xuất hiện giá trị cao bất thường.
Loudness có ngưỡng dưới -25.763 và ngưỡng trên 3.965, IQR = 7.432. Đây là biến có phạm vi rộng và tiềm năng xuất hiện outlier lớn, do giá trị âm sâu bất thường có thể xuất hiện.
Speechiness với IQR = 0.041, ngưỡng trên 0.137, ngưỡng dưới -0.026, cho thấy phần lớn dữ liệu tập trung thấp, nhưng các giá trị vượt ngưỡng trên là cực đoan rõ rệt, phù hợp với nhận xét trước đó về outlier cao trong biến này.
Cuối cùng, tempo có IQR = 42.116, ngưỡng từ 30.247 đến 198.711, phản ánh sự phân tán đáng kể, với khả năng xuất hiện giá trị cực thấp hoặc cực cao khá cao so với phần còn lại của dữ liệu.
Nhìn chung, các biến như speechiness, loudness, acousticness có khả năng xuất hiện các giá trị cực đoan cao nhất, điều này phù hợp với kết quả phân tích skewness và kurtosis trước đó. Các biến còn lại vẫn có một số outlier nhưng ở mức vừa phải.
# Tính số lượng và tỷ lệ phần trăm outlier dựa trên ngưỡng IQR
outlier_percentage_df_g1 <- lapply(outlier_results_list_g1, function(res) {
x <- spotify_group1[[res$var_name]] %>% na.omit()
Num_Outliers_Low <- sum(x < res$Lower_Bound)
Num_Outliers_High <- sum(x > res$Upper_Bound)
Total_Outliers <- Num_Outliers_Low + Num_Outliers_High
Total_NonNA <- length(x)
Percentage_Outliers <- round((Total_Outliers / Total_NonNA) * 100, 2)
data.frame(
Variable = res$var_name,
Num_Outliers_Low = Num_Outliers_Low,
Num_Outliers_High = Num_Outliers_High,
Total_Outliers = Total_Outliers,
Total_NonNA = Total_NonNA,
Percentage_Outliers = Percentage_Outliers
)
}) %>% do.call(rbind, .)
# Hiển thị bảng
kable(outlier_percentage_df_g1,
caption = "Bảng: Số lượng và tỷ lệ phần trăm giá trị ngoại lai (Nhóm Đặc trưng kỹ thuật âm nhạc)",
col.names = c("Tên Biến", "Outlier Dưới", "Outlier Trên", "Tổng Outlier", "Tổng QS (Không NA)", "Tỷ lệ Outlier (%)"),
format = "pipe", align = "l")| Tên Biến | Outlier Dưới | Outlier Trên | Tổng Outlier | Tổng QS (Không NA) | Tỷ lệ Outlier (%) |
|---|---|---|---|---|---|
| danceability | 143 | 0 | 143 | 170653 | 0.08 |
| energy | 0 | 0 | 0 | 170653 | 0.00 |
| valence | 0 | 0 | 0 | 170653 | 0.00 |
| acousticness | 0 | 0 | 0 | 170653 | 0.00 |
| instrumentalness | 0 | 36105 | 36105 | 170653 | 21.16 |
| liveness | 0 | 11808 | 11808 | 170653 | 6.92 |
| loudness | 3501 | 0 | 3501 | 170653 | 2.05 |
| speechiness | 0 | 23937 | 23937 | 170653 | 14.03 |
| tempo | 143 | 1502 | 1645 | 170653 | 0.96 |
Dựa trên bảng số liệu về giá trị ngoại lai (outlier) trong Nhóm biến Đặc trưng kỹ thuật âm nhạc, có thể nhận xét như sau:
Biến instrumentalness là biến có tỷ lệ outlier cao nhất, với 36.105 giá trị ngoại lai, chiếm 21.16% tổng số quan sát. Tất cả outlier đều nằm trên ngưỡng trên, cho thấy sự tồn tại của nhiều bản ghi có instrumentalness cực cao so với phần còn lại của dữ liệu. Điều này có thể phản ánh rằng một số bản nhạc hoàn toàn mang tính instrumental, khác biệt rõ rệt với phần lớn các bản nhạc còn lại.
Biến speechiness cũng có tỷ lệ outlier đáng chú ý, với 23.937 giá trị ngoại lai, chiếm 14.03% tổng quan sát, tất cả nằm trên ngưỡng trên. Điều này chỉ ra rằng một số bản nhạc có mức độ “nói” (speechiness) cực cao, khác biệt với phần lớn các bản nhạc.
Biến liveness có 11.808 outlier, chiếm 6.92%, cũng chủ yếu nằm trên ngưỡng trên, cho thấy có một số bản nhạc có tính “sống” (liveness) rất cao, có thể là bản thu trực tiếp hoặc có âm thanh khán giả rõ rệt.
Các biến như loudness có 3.501 outlier dưới, chiếm 2.05%, cho thấy một số bản nhạc khá “nhẹ” so với phần lớn dữ liệu. Trong khi đó, các biến danceability, tempo có một số ít outlier (0.08% và 0.96%), còn các biến energy, valence, acousticness hầu như không có outlier.
Nhìn chung, instrumentalness, speechiness và liveness là những biến có mức độ biến động và sự tồn tại outlier đáng kể nhất trong nhóm, phản ánh sự khác biệt đặc trưng của một số bản nhạc so với phần còn lại của tập dữ liệu. Các biến khác như energy, valence, acousticness tương đối ổn định, gần như không có outlier.
# 1. Xác định ngưỡng outlier cho instrumentalness
instr_outlier_info <- identify_outliers_iqr(spotify_group1, "instrumentalness")
instr_lower_bound <- instr_outlier_info$Lower_Bound
instr_upper_bound <- instr_outlier_info$Upper_Bound
cat(paste("Ngưỡng Outlier cho instrumentalness: Dưới", round(instr_lower_bound, 3),
"và Trên", round(instr_upper_bound, 3), "\n"))Ngưỡng Outlier cho instrumentalness: Dưới -0.153 và Trên 0.255
# 2. Tạo data frame chỉ chứa các outlier của instrumentalness
instr_outliers <- spotify_group1 %>%
filter(instrumentalness < instr_lower_bound | instrumentalness > instr_upper_bound)
# 3. Phân tích đặc điểm: Tần suất theo Năm phát hành (release_year)
outlier_by_year <- instr_outliers %>%
count(release_year, name = "SoLuongOutlier") %>% # Đếm số outlier theo năm
mutate(TyLeTrongOutlier = round((SoLuongOutlier / sum(SoLuongOutlier)) * 100, 2)) %>% # Tính tỷ lệ %
arrange(release_year) # Sắp xếp theo năm tăng dần
# 4. Hiển thị bảng outlier theo năm phát hành
kable(outlier_by_year,
caption = "Bảng: Phân bổ Outlier của Instrumentalness theo Năm phát hành",
col.names = c("Năm Phát Hành", "Số lượng Outlier", "Tỷ lệ trong Tổng Outlier (%)"),
format = "pipe", align = "l")| Năm Phát Hành | Số lượng Outlier | Tỷ lệ trong Tổng Outlier (%) |
|---|---|---|
| 1921 | 64 | 0.18 |
| 1922 | 37 | 0.10 |
| 1923 | 82 | 0.23 |
| 1924 | 155 | 0.43 |
| 1925 | 138 | 0.38 |
| 1926 | 547 | 1.52 |
| 1927 | 309 | 0.86 |
| 1928 | 748 | 2.07 |
| 1929 | 280 | 0.78 |
| 1930 | 872 | 2.42 |
| 1931 | 263 | 0.73 |
| 1932 | 151 | 0.42 |
| 1933 | 165 | 0.46 |
| 1934 | 201 | 0.56 |
| 1935 | 440 | 1.22 |
| 1936 | 358 | 0.99 |
| 1937 | 269 | 0.75 |
| 1938 | 309 | 0.86 |
| 1939 | 364 | 1.01 |
| 1940 | 806 | 2.23 |
| 1941 | 514 | 1.42 |
| 1942 | 825 | 2.29 |
| 1943 | 367 | 1.02 |
| 1944 | 419 | 1.16 |
| 1945 | 698 | 1.93 |
| 1946 | 1019 | 2.82 |
| 1947 | 737 | 2.04 |
| 1948 | 860 | 2.38 |
| 1949 | 747 | 2.07 |
| 1950 | 612 | 1.70 |
| 1951 | 755 | 2.09 |
| 1952 | 704 | 1.95 |
| 1953 | 767 | 2.12 |
| 1954 | 735 | 2.04 |
| 1955 | 551 | 1.53 |
| 1956 | 580 | 1.61 |
| 1957 | 472 | 1.31 |
| 1958 | 466 | 1.29 |
| 1959 | 465 | 1.29 |
| 1960 | 430 | 1.19 |
| 1961 | 633 | 1.75 |
| 1962 | 411 | 1.14 |
| 1963 | 461 | 1.28 |
| 1964 | 387 | 1.07 |
| 1965 | 282 | 0.78 |
| 1966 | 290 | 0.80 |
| 1967 | 312 | 0.86 |
| 1968 | 306 | 0.85 |
| 1969 | 419 | 1.16 |
| 1970 | 343 | 0.95 |
| 1971 | 328 | 0.91 |
| 1972 | 296 | 0.82 |
| 1973 | 259 | 0.72 |
| 1974 | 299 | 0.83 |
| 1975 | 296 | 0.82 |
| 1976 | 312 | 0.86 |
| 1977 | 333 | 0.92 |
| 1978 | 260 | 0.72 |
| 1979 | 305 | 0.84 |
| 1980 | 330 | 0.91 |
| 1981 | 371 | 1.03 |
| 1982 | 259 | 0.72 |
| 1983 | 385 | 1.07 |
| 1984 | 369 | 1.02 |
| 1985 | 294 | 0.81 |
| 1986 | 289 | 0.80 |
| 1987 | 267 | 0.74 |
| 1988 | 298 | 0.83 |
| 1989 | 298 | 0.83 |
| 1990 | 332 | 0.92 |
| 1991 | 291 | 0.81 |
| 1992 | 265 | 0.73 |
| 1993 | 309 | 0.86 |
| 1994 | 301 | 0.83 |
| 1995 | 325 | 0.90 |
| 1996 | 249 | 0.69 |
| 1997 | 276 | 0.76 |
| 1998 | 283 | 0.78 |
| 1999 | 239 | 0.66 |
| 2000 | 255 | 0.71 |
| 2001 | 268 | 0.74 |
| 2002 | 222 | 0.61 |
| 2003 | 202 | 0.56 |
| 2004 | 196 | 0.54 |
| 2005 | 227 | 0.63 |
| 2006 | 195 | 0.54 |
| 2007 | 183 | 0.51 |
| 2008 | 156 | 0.43 |
| 2009 | 184 | 0.51 |
| 2010 | 207 | 0.57 |
| 2011 | 259 | 0.72 |
| 2012 | 210 | 0.58 |
| 2013 | 245 | 0.68 |
| 2014 | 195 | 0.54 |
| 2015 | 261 | 0.72 |
| 2016 | 204 | 0.57 |
| 2017 | 229 | 0.63 |
| 2018 | 143 | 0.40 |
| 2019 | 179 | 0.50 |
| 2020 | 42 | 0.12 |
Biến instrumentalness (mức độ nhạc cụ, đo lường khả năng một bản nhạc không có lời hát) trong bộ dữ liệu có một số giá trị ngoại lai, với ngưỡng dưới là -0.153 và ngưỡng trên là 0.255. Tổng cộng có 7,131 outlier, chiếm 19.72% tổng số quan sát hợp lệ.
Xét theo năm phát hành, outlier phân bố không đều: các năm 1946, 1948, 1953 và các năm đầu thập niên 1930-1950 có số lượng outlier cao, trong khi các năm gần đây (2018-2020) có rất ít outlier. Điều này có thể phản ánh xu hướng thay đổi đặc trưng instrumentalness của các bản nhạc qua các thập kỷ.
## 1. Tính Mean và SD gốc của instrumentalness
mean_instr_orig <- mean(spotify_group1$instrumentalness, na.rm = TRUE)
sd_instr_orig <- sd(spotify_group1$instrumentalness, na.rm = TRUE)
## 2. Loại bỏ outlier dựa trên ngưỡng IQR đã xác định
instr_no_outliers <- spotify_group1 %>%
filter(!is.na(instrumentalness) &
instrumentalness >= instr_lower_bound &
instrumentalness <= instr_upper_bound)
## 3. Tính Mean và SD sau khi loại bỏ outlier
mean_instr_no_out <- mean(instr_no_outliers$instrumentalness, na.rm = TRUE)
sd_instr_no_out <- sd(instr_no_outliers$instrumentalness, na.rm = TRUE)
## 4. Tạo bảng so sánh
impact_instr_df <- data.frame(
Metric = c("Mean (Trung bình)", "SD (Độ lệch chuẩn)"),
Original = round(c(mean_instr_orig, sd_instr_orig), 3),
Without_Outliers = round(c(mean_instr_no_out, sd_instr_no_out), 3)
)
## 5. Hiển thị bảng
kable(impact_instr_df,
caption = "Bảng: Ảnh hưởng của Outlier lên Mean và SD biến Instrumentalness",
col.names = c("Chỉ số Thống kê", "Giá trị Gốc", "Giá trị Sau khi Loại bỏ Outlier"),
format = "pipe", align = "l")| Chỉ số Thống kê | Giá trị Gốc | Giá trị Sau khi Loại bỏ Outlier |
|---|---|---|
| Mean (Trung bình) | 0.167 | 0.013 |
| SD (Độ lệch chuẩn) | 0.313 | 0.040 |
Mean (Trung bình) giảm mạnh từ 0.167 xuống 0.013 sau khi loại bỏ outlier, cho thấy các giá trị ngoại lai có ảnh hưởng lớn đến mức trung bình của instrumentalness.
SD (Độ lệch chuẩn) giảm từ 0.313 xuống 0.040, chứng tỏ các outlier cực đoan làm tăng đáng kể độ biến thiên của dữ liệu.
=> instrumentalness có nhiều giá trị ngoại lai ảnh hưởng mạnh đến các chỉ số thống kê cơ bản, nên việc loại bỏ outlier là cần thiết trước khi phân tích sâu hơn hoặc xây dựng mô hình.
Sau khi nắm rõ phân bố của từng biến, mục này nghiên cứu mối quan hệ tuyến tính giữa các đặc trưng kỹ thuật âm nhạc định lượng. Hệ số tương quan Pearson được sử dụng để đánh giá mức độ và chiều hướng liên kết, từ đó xác định các xu hướng hoặc sự tương tác giữa các thuộc tính như mức độ nhạc cụ (instrumentalness), cảm xúc âm nhạc (valence), năng lượng (energy), độ sôi động (danceability) hay độ vang âm (loudness).
# Chọn các biến numeric để tính tương quan
cor_vars_instr <- c("danceability", "energy", "valence", "acousticness",
"instrumentalness", "liveness", "loudness", "speechiness",
"tempo", "energy_dance_ratio")
# Tạo data frame numeric
cor_data_instr <- spotify_group1 %>%
select(all_of(cor_vars_instr)) %>%
na.omit() # loại bỏ NA
# Kiểm tra
str(cor_data_instr)Classes ‘data.table’ and ‘data.frame’: 170644 obs. of 10 variables: $
danceability : num 0.279 0.819 0.328 0.275 0.418 0.697 0.518 0.389 0.485
0.684 … $ energy : num 0.211 0.341 0.166 0.309 0.193 0.346 0.203 0.088
0.13 0.257 … $ valence : num 0.0594 0.963 0.0394 0.165 0.253 0.196 0.406
0.0731 0.721 0.771 … $ acousticness : num 0.982 0.732 0.961 0.967 0.957
0.579 0.996 0.993 0.996 0.982 … $ instrumentalness : num 8.78e-01 0.00
9.13e-01 2.77e-05 1.68e-06 1.68e-01 0.00 5.27e-01 1.51e-01 0.00 … $
liveness : num 0.665 0.16 0.101 0.381 0.229 0.13 0.115 0.363 0.104 0.504
… $ loudness : num -20.1 -12.44 -14.85 -9.32 -10.1 … $ speechiness : num
0.0366 0.415 0.0339 0.0354 0.038 0.07 0.0615 0.0456 0.0483 0.399 … $
tempo : num 81 60.9 110.3 100.1 101.7 … $ energy_dance_ratio: num 0.756
0.416 0.506 1.124 0.462 … - attr(*,
“.internal.selfref”)=
# Tính ma trận tương quan Pearson
cor_matrix_instr <- cor(cor_data_instr, method = "pearson")
# Hiển thị
cor_matrix_instr danceability energy valence acousticness
danceability 1.000000000 0.22175035 0.558819907 -0.26714512 energy 0.221750349 1.00000000 0.353752989 -0.74961939 valence 0.558819907 0.35375299 1.000000000 -0.18427075 acousticness -0.267145117 -0.74961939 -0.184270752 1.00000000 instrumentalness -0.278219613 -0.28117827 -0.198580019 0.32979978 liveness -0.100410796 0.12609525 0.003708148 -0.02456746 loudness 0.284300165 0.78311890 0.313242446 -0.56340157 speechiness 0.235454197 -0.07061908 0.046322306 -0.04402539 tempo 0.001188986 0.25062072 0.171369152 -0.20747670 energy_dance_ratio NaN NaN NaN NaN instrumentalness liveness loudness speechiness danceability -0.27821961 -0.100410796 0.28430016 0.23545420 energy -0.28117827 0.126095246 0.78311890 -0.07061908 valence -0.19858002 0.003708148 0.31324245 0.04632231 acousticness 0.32979978 -0.024567464 -0.56340157 -0.04402539 instrumentalness 1.00000000 -0.047227815 -0.40963810 -0.12171891 liveness -0.04722781 1.000000000 0.05600204 0.13463610 loudness -0.40963810 0.056002044 1.00000000 -0.13983643 speechiness -0.12171891 0.134636102 -0.13983643 1.00000000 tempo -0.10550941 0.007480293 0.20854378 -0.01164909 energy_dance_ratio NaN NaN NaN NaN tempo energy_dance_ratio danceability 0.001188986 NaN energy 0.250620720 NaN valence 0.171369152 NaN acousticness -0.207476701 NaN instrumentalness -0.105509410 NaN liveness 0.007480293 NaN loudness 0.208543784 NaN speechiness -0.011649091 NaN tempo 1.000000000 NaN energy_dance_ratio NaN 1
##
## Attaching package: 'reshape2'
## The following object is masked from 'package:tidyr':
##
## smiths
## The following objects are masked from 'package:data.table':
##
## dcast, melt
library(dplyr)
library(knitr)
# 1. Chuyển đổi ma trận tương quan Instrumentalness thành dạng dài
melted_cor_instr <- melt(cor_matrix_instr, na.rm = TRUE)
# 2. Loại bỏ cặp trùng lặp và tự tương quan
melted_cor_unique_instr <- melted_cor_instr %>%
filter(as.character(Var1) < as.character(Var2))
# 3. Sắp xếp theo giá trị tương quan tuyệt đối giảm dần
strongest_cor_instr <- melted_cor_unique_instr %>%
arrange(desc(abs(value)))
# 4. Lấy Top 5 cặp tương quan mạnh nhất
top_5_cor_instr <- head(strongest_cor_instr, 5) %>%
mutate(value = round(value, 3))
# 5. Hiển thị bảng
kable(top_5_cor_instr,
caption = "Bảng: Top 5 cặp tương quan tuyến tính mạnh nhất của Instrumentalness với các đặc trưng kỹ thuật âm nhạc",
col.names = c("Biến 1", "Biến 2", "Hệ số Tương quan (r)"),
format = "pipe", align = "l")| Biến 1 | Biến 2 | Hệ số Tương quan (r) |
|---|---|---|
| energy | loudness | 0.783 |
| acousticness | energy | -0.750 |
| acousticness | loudness | -0.563 |
| danceability | valence | 0.559 |
| instrumentalness | loudness | -0.410 |
# Top 3 cặp tương quan dương mạnh nhất (không tính cặp hoàn hảo)
top_3_pos_cor_instr <- strongest_cor_instr %>%
filter(value > 0 & value < 1) %>%
head(3) %>%
mutate(value = round(value, 3))
kable(top_3_pos_cor_instr,
caption = "Bảng: Top 3 cặp tương quan dương mạnh nhất của Instrumentalness với các đặc trưng kỹ thuật âm nhạc",
col.names = c("Biến 1", "Biến 2", "Hệ số Tương quan (r)"),
format = "pipe", align = "l")| Biến 1 | Biến 2 | Hệ số Tương quan (r) |
|---|---|---|
| energy | loudness | 0.783 |
| danceability | valence | 0.559 |
| energy | valence | 0.354 |
# Top 3 cặp tương quan âm mạnh nhất
top_3_neg_cor_instr <- strongest_cor_instr %>%
filter(value < 0) %>%
arrange(value) %>%
head(3) %>%
mutate(value = round(value, 3))
# Kiểm tra và hiển thị
if(nrow(top_3_neg_cor_instr) > 0) {
kable(top_3_neg_cor_instr,
caption = "Bảng: Top 3 cặp tương quan âm mạnh nhất của Instrumentalness với các đặc trưng kỹ thuật âm nhạc",
col.names = c("Biến 1", "Biến 2", "Hệ số Tương quan (r)"),
format = "pipe", align = "l")
}| Biến 1 | Biến 2 | Hệ số Tương quan (r) |
|---|---|---|
| acousticness | energy | -0.750 |
| acousticness | loudness | -0.563 |
| instrumentalness | loudness | -0.410 |
library(purrr)
library(broom)
library(dplyr)
library(knitr)
# Chọn các cặp kiểm định (có Instrumentalness hoặc các biến numeric nhóm kỹ thuật âm nhạc)
pairs_to_test_instr <- list(
c("instrumentalness", "danceability"),
c("instrumentalness", "energy"),
c("instrumentalness", "loudness"),
c("instrumentalness", "valence"),
c("instrumentalness", "tempo")
)
# Lặp qua các cặp và thực hiện cor.test
cor_test_results_instr <- map_dfr(pairs_to_test_instr, function(pair) {
var1 <- pair[1]
var2 <- pair[2]
pair_label <- paste(var1, "vs", var2)
# Lấy dữ liệu numeric hợp lệ không NA/Inf
pair_data <- spotify_group1 %>%
select(all_of(c(var1, var2))) %>%
filter(across(everything(), ~ !is.na(.) & !is.infinite(.))) %>%
mutate(across(everything(), as.numeric))
if(nrow(pair_data) >= 3) {
test_result <- tryCatch({
cor.test(pair_data[[var1]], pair_data[[var2]], method = "pearson") %>%
tidy() %>%
select(Correlation = estimate, P_Value = p.value, DF = parameter) %>%
mutate(across(where(is.numeric), ~ round(.x, 3)))
}, error = function(e) {
tibble(Correlation = NA_real_, P_Value = NA_real_, DF = NA_integer_)
})
} else {
test_result <- tibble(Correlation = NA_real_, P_Value = NA_real_, DF = NA_integer_)
}
test_result %>% add_column(Pair = pair_label, .before = 1)
})## Warning: Using `across()` in `filter()` was deprecated in dplyr 1.0.8.
## ℹ Please use `if_any()` or `if_all()` instead.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: Using `across()` in `filter()` was deprecated in dplyr 1.0.8.
## ℹ Please use `if_any()` or `if_all()` instead.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: Using `across()` in `filter()` was deprecated in dplyr 1.0.8.
## ℹ Please use `if_any()` or `if_all()` instead.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: Using `across()` in `filter()` was deprecated in dplyr 1.0.8.
## ℹ Please use `if_any()` or `if_all()` instead.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: Using `across()` in `filter()` was deprecated in dplyr 1.0.8.
## ℹ Please use `if_any()` or `if_all()` instead.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
# Định dạng P-value và thêm cột kết luận ý nghĩa
cor_test_final_instr <- cor_test_results_instr %>%
mutate(P_Value_Formatted = format.pval(P_Value, digits = 3, eps = 0.001),
Significance = case_when(
is.na(P_Value) ~ "Lỗi/NA",
P_Value < 0.05 ~ "Có ý nghĩa (p < 0.05)",
TRUE ~ "Không ý nghĩa (p >= 0.05)"
))
# Hiển thị bảng kiểm định
kable(cor_test_final_instr %>% select(Pair, Correlation, P_Value_Formatted, Significance, DF),
caption = "Bảng: Kết quả kiểm định ý nghĩa thống kê cho các tương quan Instrumentalness",
col.names = c("Cặp Biến", "Hệ số r", "P-value", "Kết luận", "Bậc tự do (df)"),
format = "pipe", align = "l")| Cặp Biến | Hệ số r | P-value | Kết luận | Bậc tự do (df) |
|---|---|---|---|---|
| instrumentalness vs danceability | -0.278 | <0.001 | Có ý nghĩa (p < 0.05) | 170651 |
| instrumentalness vs energy | -0.281 | <0.001 | Có ý nghĩa (p < 0.05) | 170651 |
| instrumentalness vs loudness | -0.409 | <0.001 | Có ý nghĩa (p < 0.05) | 170651 |
| instrumentalness vs valence | -0.199 | <0.001 | Có ý nghĩa (p < 0.05) | 170651 |
| instrumentalness vs tempo | -0.105 | <0.001 | Có ý nghĩa (p < 0.05) | 170651 |
Mục 1.3.2. phân tích mối quan hệ giữa các đặc trưng kỹ thuật âm nhạc để làm rõ cách các thuộc tính tương tác và ảnh hưởng lẫn nhau. Việc sử dụng hệ số tương quan Pearson giúp xác định cường độ và chiều hướng liên kết giữa các biến numeric, từ đó nhận diện các quy luật hay xu hướng âm nhạc chung.
Các cặp tương quan dương mạnh nhất phản ánh các thuộc tính thường đi kèm nhau. Cụ thể, energy và loudness có mối tương quan dương rất mạnh (r = 0.783), cho thấy các bài hát năng lượng cao thường có âm lượng lớn. Danceability và valence cũng có tương quan dương đáng kể (r = 0.559), điều này nhấn mạnh rằng những bài hát dễ nhảy thường mang cảm xúc tích cực. Ngoài ra, energy và valence cũng có tương quan vừa phải (r = 0.354), gợi ý rằng các bài hát năng động thường có cảm giác vui vẻ hoặc lạc quan hơn.
Các tương quan âm mạnh chỉ ra các thuộc tính có xu hướng đối lập. Acousticness và energy có tương quan âm cao (-0.750), tương tự acousticness với loudness (-0.563), chứng tỏ các bài hát mang tính acoustic thường ít năng lượng và âm lượng vừa phải. Instrumentalness cũng có tương quan âm với loudness (-0.410), energy (-0.281) và danceability (-0.278), phản ánh nhạc nhiều nhạc cụ thường nhịp độ nhẹ nhàng và ít sôi động. Những mối quan hệ âm này giúp phân biệt các nhóm nhạc cụ, phong cách acoustic và nhạc điện tử hoặc nhạc sôi động.
Các tương quan yếu hơn vẫn có ý nghĩa thống kê, ví dụ: tempo gần như không liên quan nhiều với hầu hết các biến khác, liveness và speechiness có tương quan nhẹ với energy, loudness và danceability. Điều này gợi ý rằng các đặc trưng về độ sống động của buổi biểu diễn và mức độ lời nói không ảnh hưởng mạnh đến nhịp điệu hay năng lượng tổng thể của bài hát.
Kiểm định ý nghĩa thống kê cho thấy tất cả các cặp tương quan chính đều có p-value < 0.001, cho thấy các mối quan hệ này là đáng tin cậy và không xảy ra do ngẫu nhiên. Nhờ đó, các kết luận về mối tương quan âm-dương, mức độ mạnh-yếu của từng cặp biến có cơ sở thống kê vững chắc.
Nhìn chung, kết quả cho thấy các đặc trưng kỹ thuật âm nhạc không hoạt động độc lập mà thường tương tác lẫn nhau: năng lượng, âm lượng, valence và danceability có xu hướng đi cùng nhau, trong khi acousticness và instrumentalness thường đi ngược lại với các yếu tố sôi động và nhịp điệu mạnh. Nhận xét này quan trọng cho việc phân loại bài hát, mô hình đề xuất nhạc, hoặc phân tích hành vi nghe nhạc dựa trên đặc trưng kỹ thuật.
Các thông tin chi tiết về các cặp tương quan dương/âm mạnh nhất, cũng như kiểm định ý nghĩa, giúp minh họa rõ ràng rằng không chỉ một vài biến quyết định đặc trưng âm nhạc mà là sự kết hợp của nhiều thuộc tính. Việc nắm được mối quan hệ giữa các biến này hỗ trợ cả việc phân tích dữ liệu âm nhạc lẫn ứng dụng thực tiễn trong hệ thống gợi ý, playlist tự động, hoặc nghiên cứu hành vi người nghe.
Sau khi đã khảo sát phân bố và mối quan hệ giữa các đặc trưng kỹ thuật âm nhạc, mục này tập trung vào việc kiểm tra xem giá trị trung bình của các biến định lượng như danceability, energy, valence có khác nhau giữa các nhóm phân loại của bài hát hay không (ví dụ: theo thể loại nhạc, nhóm nhạc hoặc mức độ instrumentalness).
Ý tưởng là sử dụng các phép kiểm định thống kê (t-test hoặc ANOVA) để đánh giá sự khác biệt trung bình giữa các nhóm. Giả thuyết gốc H0 là “không có sự khác biệt trung bình giữa các nhóm”. Nếu p-value < 0.05, H0 bị bác bỏ, chứng tỏ có sự khác biệt đáng kể về đặc trưng kỹ thuật giữa các nhóm.
Kết quả giúp hiểu rõ hơn sự phân hóa của các đặc trưng âm nhạc theo nhóm, đồng thời cung cấp cơ sở cho việc phân loại, dự đoán thể loại hoặc đặc tính bài hát dựa trên các đặc trưng kỹ thuật.
library(dplyr)
# --- t-test cho investment (2 nhóm) ---
if (all(c("danceability","investment") %in% names(data))) {
t_test_inv <- t.test(danceability ~ investment, data = data)
cat("## T-test: danceability ~ investment\n")
print(t_test_inv)
}
# --- ANOVA cho sector ---
if (all(c("danceability","sector") %in% names(data))) {
anova_sec <- aov(danceability ~ sector, data = data)
cat("\n## ANOVA: danceability ~ sector\n")
print(summary(anova_sec))
if (summary(anova_sec)[[1]][["Pr(>F)"]][1] < 0.05) {
cat("\n### TukeyHSD: danceability ~ sector\n")
print(TukeyHSD(anova_sec))
}
}
# --- ANOVA cho Holding_Period ---
if (all(c("danceability","Holding_Period") %in% names(data))) {
anova_hp <- aov(danceability ~ Holding_Period, data = data)
cat("\n## ANOVA: danceability ~ Holding_Period\n")
print(summary(anova_hp))
if (summary(anova_hp)[[1]][["Pr(>F)"]][1] < 0.05) {
cat("\n### TukeyHSD: danceability ~ Holding_Period\n")
print(TukeyHSD(anova_hp))
}
}
# --- ANOVA cho Risk_Level ---
if (all(c("danceability","Risk_Level") %in% names(data))) {
anova_rl <- aov(danceability ~ Risk_Level, data = data)
cat("\n## ANOVA: danceability ~ Risk_Level\n")
print(summary(anova_rl))
if (summary(anova_rl)[[1]][["Pr(>F)"]][1] < 0.05) {
cat("\n### TukeyHSD: danceability ~ Risk_Level\n")
print(TukeyHSD(anova_rl))
}
}
# --- ANOVA cho Amount_Level ---
if (all(c("danceability","Amount_Level") %in% names(data))) {
anova_al <- aov(danceability ~ Amount_Level, data = data)
cat("\n## ANOVA: danceability ~ Amount_Level\n")
print(summary(anova_al))
if (summary(anova_al)[[1]][["Pr(>F)"]][1] < 0.05) {
cat("\n### TukeyHSD: danceability ~ Amount_Level\n")
print(TukeyHSD(anova_al))
}
}Biến chính được trực quan hóa: danceability, energy, valence, acousticness, instrumentalness, liveness, loudness, speechiness, tempo, key, mode, energy_dance_ratio. Các biến này phản ánh đặc điểm kỹ thuật của bài hát, bao gồm mức độ dễ nhảy, năng lượng, cảm xúc (vui/buồn), tính chất acoustic, mức độ lời nói, sự sống động trong âm thanh, cường độ, nhịp độ, khóa, mode và tỷ lệ năng lượng/danceability.
Dữ liệu sử dụng: group1_vars.
Biểu đồ mật độ (Density Plot) được sử dụng để so sánh hình dạng phân bố của hai đặc tính kỹ thuật âm nhạc, danceability và energy, giúp làm rõ sự khác biệt về mức độ dễ nhảy và năng lượng của các bài hát trong bộ dữ liệu.t.
##
## Attaching package: 'ggplot2'
## The following object is masked from 'package:e1071':
##
## element
return_comparison_long_viz <- data %>%
select(danceability, energy) %>% # chọn 2 đặc tính để so sánh
filter(!is.na(danceability), !is.na(energy)) %>%
pivot_longer(cols = everything(), names_to = "Feature", values_to = "Value")
# 2. Vẽ Biểu đồ mật độ
p1411 <- ggplot(return_comparison_long_viz, aes(x = Value, fill = Feature)) +
geom_density(alpha = 0.6) + # vẽ đường mật độ
labs(title = "Biểu đồ 1.4.1: So sánh phân bố các đặc tính kỹ thuật",
subtitle = "Phân bố Mật độ Danceability vs Energy",
x = "Giá trị", y = "Mật độ ước lượng", fill = "Đặc tính:") +
theme_minimal(base_size = 11) +
theme(legend.position = "bottom")
# 3. Hiển thị biểu đồ
print(p1411)Phân bố danceability có đỉnh cao khoảng giá trị 0.6–0.7, tức phần lớn các bài hát trong bộ dữ liệu có độ “dễ nhảy” ở mức trung bình cao.
Phân bố energy rộng hơn và hơi lệch trái, tập trung khoảng 0.3–0.5, cho thấy đa số bài hát có mức năng lượng vừa phải, ít bài hát cực kỳ năng động.
So sánh hai đặc tính: danceability có xu hướng cao hơn energy, tức là nhiều bài hát dễ nhảy nhưng không nhất thiết quá mạnh mẽ về năng lượng.
Hai đường mật độ có hình dạng khác nhau, cho thấy sự khác biệt trong phân bố của hai đặc tính. Biểu đồ cũng cho thấy không có giá trị bất thường cực đoan (outlier) vượt quá 0–1, vì cả hai biến đều đã được chuẩn hóa.
Kết luận: Biểu đồ này giúp trực quan hóa đặc điểm kỹ thuật âm nhạc chính của bộ dữ liệu, thấy được rằng các bài hát dễ nhảy thường có mức năng lượng vừa phải, không quá cao, và phân bố các đặc tính này không đồng nhất.
library(dplyr)
library(tidyr)
library(ggplot2)
# Giả sử data$artists là list/vector trong mỗi ô
data_long <- data %>%
unnest_longer(artists) # tách list thành từng dòng
# Tính số bài hát và popularity trung bình theo nghệ sĩ
artist_pop_viz <- data_long %>%
group_by(artists) %>%
summarise(
Count = n(),
Avg_Popularity = mean(popularity, na.rm = TRUE)
) %>%
arrange(desc(Count))
# Lấy top 10 nghệ sĩ có nhiều bài hát nhất
top_artists <- artist_pop_viz %>% slice_max(Count, n = 10)
# Vẽ biểu đồ
ggplot(top_artists, aes(x = reorder(artists, Count), y = Count, fill = Avg_Popularity)) +
geom_col() +
geom_text(aes(label = Count), hjust = -0.1) +
coord_flip() +
scale_fill_gradient(low = "lightblue", high = "blue") +
labs(
title = "Top 10 Nghệ sĩ theo số bài hát",
x = NULL,
y = "Số lượng bài hát",
fill = "Popularity TB"
) +
theme_minimal()# Tạo bảng tần suất theo thập kỷ
popularity_decade_viz <- data %>%
filter(!is.na(release_decade)) %>%
count(release_decade, name = "Count") %>%
mutate(Percent = Count / sum(Count))
# Vẽ biểu đồ
library(ggplot2)
ggplot(popularity_decade_viz, aes(x = release_decade, y = Count, fill = release_decade)) +
geom_col(show.legend = FALSE) +
geom_text(aes(label = scales::comma(Count)), vjust = -0.5, size = 3.5) +
geom_text(aes(label = scales::percent(Percent, accuracy = 0.1)),
vjust = 1.8, size = 3, color = "gray20") +
geom_hline(yintercept = median(popularity_decade_viz$Count), linetype = "dashed") +
labs(title = "Phân bổ mức độ phổ biến theo thập kỷ phát hành",
x = "Thập kỷ", y = "Số lượng bài hát") +
theme_minimal()# Tạo biến phân loại từ numeric
data <- data %>%
mutate(energy_dance_level = cut(
energy_dance_ratio,
breaks = quantile(energy_dance_ratio, probs = c(0, 1/3, 2/3, 1), na.rm = TRUE),
labels = c("Thấp", "Trung bình", "Cao"),
include.lowest = TRUE
))
# Vẽ biểu đồ
ggplot(data, aes(x = popularity_level, fill = energy_dance_level)) +
geom_bar(position = "fill") + # Tỷ lệ %
scale_y_continuous(labels = scales::percent_format()) +
labs(
title = "Phân bố khả năng nhảy theo mức độ phổ biến",
x = "Mức độ phổ biến",
y = "Tỷ lệ (%)",
fill = "Khả năng nhảy"
) +
theme_minimal(base_size = 11)# 1. Tạo biến phân loại tâm trạng
data <- data %>%
mutate(valence_level = cut(
valence,
breaks = quantile(valence, probs = c(0, 1/3, 2/3, 1), na.rm = TRUE),
labels = c("Thấp", "Trung bình", "Cao"),
include.lowest = TRUE
))
# 2. Biểu đồ phân bổ tâm trạng theo thập kỷ
ggplot(data, aes(x = release_decade, fill = valence_level)) +
geom_bar(position = "fill") + # Tỷ lệ %
scale_y_continuous(labels = scales::percent_format()) +
labs(
title = "Phân bố tâm trạng bài hát theo thập kỷ phát hành",
x = "Thập kỷ phát hành",
y = "Tỷ lệ (%)",
fill = "Tâm trạng"
) +
theme_minimal(base_size = 11)ggplot(data, aes(x = release_decade, fill = is_explicit_factor)) +
geom_bar(position = "fill") + # hiển thị tỷ lệ %
scale_y_continuous(labels = scales::percent_format()) +
labs(
title = "Phân bố lời nhạc nhạy cảm theo thập kỷ phát hành",
x = "Thập kỷ phát hành",
y = "Tỷ lệ (%)",
fill = "Lời nhạy cảm"
) +
theme_minimal(base_size = 11)ggplot(data, aes(x = popularity_level, fill = tempo_category)) +
geom_bar(position = "fill") + # hiển thị tỷ lệ % của từng nhịp độ trong mỗi mức phổ biến
scale_y_continuous(labels = scales::percent_format()) +
labs(
title = "Phân bố nhịp độ bài hát theo mức độ phổ biến",
x = "Mức độ phổ biến",
y = "Tỷ lệ (%)",
fill = "Nhịp độ"
) +
theme_minimal(base_size = 11)#### **1.4.2.1. Phân tích "loudness" và "energy"**
ggplot(data, aes(x = loudness, y = energy)) +
geom_point(alpha = 0.4, color = "darkblue") +
geom_smooth(method = "lm", se = FALSE, color = "red", linetype = "dashed") +
labs(
title = "Mối quan hệ giữa Loudness và Energy",
x = "Loudness",
y = "Energy"
) +
theme_minimal(base_size = 11)## `geom_smooth()` using formula = 'y ~ x'
ggplot(data, aes(x = danceability, y = energy_dance_ratio)) +
geom_point(alpha = 0.4, color = "darkgreen") +
geom_smooth(method = "lm", se = FALSE, color = "black", linetype = "dashed") +
labs(
title = "Mối quan hệ giữa Danceability và Energy Dance Ratio",
x = "Danceability",
y = "Energy Dance Ratio"
) +
theme_minimal(base_size = 11)## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 143 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 9 rows containing missing values or values outside the scale range
## (`geom_point()`).
ggplot(data, aes(x = valence, y = energy)) +
geom_point(alpha = 0.4, color = "purple") +
geom_smooth(method = "lm", se = FALSE, color = "black", linetype = "dashed") +
labs(
title = "Mối quan hệ giữa Valence và Energy",
x = "Valence",
y = "Energy"
) +
theme_minimal(base_size = 11)## `geom_smooth()` using formula = 'y ~ x'
ggplot(data, aes(x = popularity_level, y = energy, fill = popularity_level)) +
geom_boxplot() +
labs(
title = "Phân bố Energy theo mức độ phổ biến",
x = "Mức độ phổ biến",
y = "Energy"
) +
theme_minimal(base_size = 11)ggplot(data, aes(x = release_decade, y = valence, fill = release_decade)) +
geom_boxplot() +
labs(
title = "Phân bố Valence theo thập kỷ phát hành",
x = "Thập kỷ phát hành",
y = "Valence"
) +
theme_minimal(base_size = 11)## Warning: Continuous x aesthetic
## ℹ did you forget `aes(group = ...)`?
## Warning: The following aesthetics were dropped during statistical transformation: fill.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
## the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
## variable into a factor?
ggplot(data, aes(x = popularity_level, y = tempo, fill = popularity_level)) +
geom_boxplot() +
labs(
title = "Phân bố Tempo theo mức độ phổ biến",
x = "Mức độ phổ biến",
y = "Tempo"
) +
theme_minimal(base_size = 11)ggplot(data, aes(x = popularity_level, fill = tempo_category)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent_format()) +
labs(
title = "Phân bố Tempo Category theo mức độ phổ biến",
x = "Mức độ phổ biến",
y = "Tỷ lệ (%)",
fill = "Nhịp độ"
) +
theme_minimal(base_size = 11)ggplot(data, aes(x = release_decade, fill = is_explicit_factor)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent_format()) +
labs(
title = "Phân bố lời nhạy cảm theo thập kỷ phát hành",
x = "Thập kỷ phát hành",
y = "Tỷ lệ (%)",
fill = "Lời nhạy cảm"
) +
theme_minimal(base_size = 11)ggplot(data, aes(x = release_decade, fill = valence_level)) +
geom_bar(position = "fill") +
scale_y_continuous(labels = scales::percent_format()) +
labs(
title = "Phân bố Tâm trạng bài hát theo thập kỷ phát hành",
x = "Thập kỷ phát hành",
y = "Tỷ lệ (%)",
fill = "Tâm trạng"
) +
theme_minimal(base_size = 11)# Chọn các biến numeric
vars_numeric <- c("valence", "energy", "danceability", "duration_min",
"loudness", "tempo", "acousticness", "instrumentalness", "speechiness")
# Ma trận tương quan Pearson
cor_matrix_spotify <- data %>%
select(all_of(vars_numeric)) %>%
na.omit() %>%
cor(method = "pearson")
# Hàm lấy nửa dưới
get_lower_tri <- function(cormat){
cormat[upper.tri(cormat)] <- NA
return(cormat)
}
lower_tri <- get_lower_tri(cor_matrix_spotify)
melted_cor_lower <- reshape2::melt(lower_tri, na.rm = TRUE) %>%
mutate(value = round(value, 2))
# Heatmap
ggplot(melted_cor_lower, aes(Var2, Var1, fill = value)) +
geom_tile(color = "white") +
scale_fill_gradient2(low = "blue", high = "red", mid = "white",
midpoint = 0, limit = c(-1,1), name="Tương quan\nPearson (r)") +
geom_text(aes(label = value), color = "black", size = 3) +
coord_fixed() +
labs(title = "Heatmap Ma trận tương quan các đặc trưng bài hát",
x = "Biến số", y = "Biến số") +
theme_minimal(base_size = 11) +
theme(axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1),
axis.text.y = element_text(size = 9))Bao gồm các biểu đồ Box Plot để so sánh:
Popularity theo thập kỷ phát hành
Loudness theo khả năng nhảy (energy_dance_level)
Valence theo thập kỷ phát hành
Energy theo mức độ phổ biến
##
## Attaching package: 'gridExtra'
## The following object is masked from 'package:dplyr':
##
## combine
# Hàm tạo Box Plot tiêu chuẩn
create_box_plot_music <- function(data, x_var, y_var, title, subtitle, limits_y=NULL) {
p <- ggplot(data, aes(x = .data[[x_var]], y = .data[[y_var]], fill = .data[[x_var]])) +
geom_boxplot(width = 0.5, color = "black", alpha = 0.7, outlier.alpha = 0.1) +
stat_summary(fun = "mean", geom = "point", shape = 23, size = 3, fill = "white") +
labs(title = title, subtitle = subtitle, x = "", y = y_var) +
theme_minimal(base_size = 10) + theme(legend.position = "none")
if(!is.null(limits_y)) {
p <- p + scale_y_continuous(limits = limits_y)
}
return(p)
}
# 1: Popularity theo thập kỷ phát hành
p1 <- create_box_plot_music(data, "release_decade", "popularity",
"1. Popularity theo Thập kỷ phát hành",
"Xu hướng: Thập kỷ gần đây phổ biến hơn")
# 2: Loudness theo năng lượng/khả năng nhảy
p2 <- create_box_plot_music(data, "energy_dance_level", "loudness",
"2. Loudness theo khả năng nhảy",
"Nhóm Cao có Loudness trung bình lớn hơn")
# 3: Valence theo thập kỷ phát hành
p3 <- create_box_plot_music(data, "release_decade", "valence",
"3. Valence theo Thập kỷ phát hành",
"Xu hướng tâm trạng thay đổi theo thập kỷ")
# 4: Energy theo mức độ phổ biến
p4 <- create_box_plot_music(data, "popularity_level", "energy",
"4. Energy theo Mức độ phổ biến",
"Nhóm phổ biến cao có năng lượng cao hơn")
# Gộp 4 biểu đồ
gridExtra::grid.arrange(p1, p2, p3, p4, ncol = 2)## Warning: Continuous x aesthetic
## ℹ did you forget `aes(group = ...)`?
## Warning: The following aesthetics were dropped during statistical transformation: fill.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
## the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
## variable into a factor?
## Warning: Continuous x aesthetic
## ℹ did you forget `aes(group = ...)`?
## Warning: The following aesthetics were dropped during statistical transformation: fill.
## ℹ This can happen when ggplot fails to infer the correct grouping structure in
## the data.
## ℹ Did you forget to specify a `group` aesthetic or to convert a numerical
## variable into a factor?
Bao gồm các biểu đồ để so sánh tỷ lệ hay phân bố các đặc điểm theo nhóm:
Tỷ lệ khả năng nhảy (energy_dance_level) theo mức độ phổ biến
Tỷ lệ nhịp độ (tempo_category) theo mức độ phổ biến
Tỷ lệ lời nhạy cảm (is_explicit_factor) theo mức độ phổ biến
library(ggplot2)
library(dplyr)
library(forcats)
# 1. Tỷ lệ khả năng nhảy theo mức độ phổ biến
p_energy <- ggplot(data, aes(x = popularity_level, fill = energy_dance_level)) +
geom_bar(position = "fill", width = 0.6) +
scale_y_continuous(labels = scales::percent_format()) +
scale_fill_brewer(palette = "Set2") +
labs(
title = "Tỷ lệ khả năng nhảy theo mức độ phổ biến",
x = "Mức độ phổ biến",
y = "Tỷ lệ (%)",
fill = "Khả năng nhảy"
) +
theme_minimal(base_size = 11) +
theme(axis.text.x = element_text(angle = 30, hjust = 1))
# 2. Tỷ lệ nhịp độ theo mức độ phổ biến
p_tempo <- ggplot(data, aes(x = popularity_level, fill = tempo_category)) +
geom_bar(position = "fill", width = 0.6) +
scale_y_continuous(labels = scales::percent_format()) +
scale_fill_brewer(palette = "Set3") +
labs(
title = "Tỷ lệ nhịp độ bài hát theo mức độ phổ biến",
x = "Mức độ phổ biến",
y = "Tỷ lệ (%)",
fill = "Nhịp độ"
) +
theme_minimal(base_size = 11) +
theme(axis.text.x = element_text(angle = 30, hjust = 1))
# 3. Tỷ lệ lời nhạy cảm theo mức độ phổ biến
p_explicit <- ggplot(data, aes(x = popularity_level, fill = is_explicit_factor)) +
geom_bar(position = "fill", width = 0.6) +
scale_y_continuous(labels = scales::percent_format()) +
labs(
title = "Tỷ lệ lời nhạy cảm theo mức độ phổ biến",
x = "Mức độ phổ biến",
y = "Tỷ lệ (%)",
fill = "Lời nhạy cảm"
) +
theme_minimal(base_size = 11) +
theme(axis.text.x = element_text(angle = 30, hjust = 1))
# Gộp cả 3 biểu đồ
library(gridExtra)
grid.arrange(p_energy, p_tempo, p_explicit, ncol = 1)# Dữ liệu theo năm
song_year_trend <- data %>%
filter(!is.na(release_year)) %>%
count(release_year, name = "Count") %>%
arrange(release_year)
# Dữ liệu theo thập kỷ
song_decade_trend <- data %>%
filter(!is.na(release_decade)) %>%
count(release_decade, name = "Count") %>%
arrange(release_decade)
# Biểu đồ số lượng bài hát theo năm (xoay nhãn, hiện label cách 2-3 năm)
p_year <- ggplot(song_year_trend, aes(x = factor(release_year), y = Count)) +
geom_col(fill = "#1F77B4") +
geom_text(aes(label = Count), vjust = -0.5, size = 2.5,
check_overlap = TRUE) + # tự bỏ label chồng
labs(title = "Số lượng bài hát theo năm", x = "Năm phát hành", y = "Số lượng") +
theme_minimal(base_size = 10) +
theme(axis.text.x = element_text(angle = 45, hjust = 1, size = 7))
# Biểu đồ số lượng bài hát theo thập kỷ
p_decade <- ggplot(song_decade_trend, aes(x = release_decade, y = Count)) +
geom_col(fill = "#D62728") +
geom_text(aes(label = Count), vjust = -0.5, size = 3) +
labs(title = "Số lượng bài hát theo thập kỷ", x = "Thập kỷ", y = "Số lượng") +
theme_minimal(base_size = 10)
# Gộp 2 biểu đồ
gridExtra::grid.arrange(p_year, p_decade, ncol = 2)Bộ dữ liệu Spotify 1921–2020 cung cấp thông tin chi tiết về các bài hát trong gần một thế kỷ, bao gồm các đặc trưng âm nhạc, thông tin nghệ sĩ, năm phát hành và mức độ phổ biến. Dữ liệu đã được làm sạch và chuẩn hóa, không tồn tại giá trị thiếu, đảm bảo tính khả thi cho các phân tích thống kê và trực quan hóa tiếp theo.
Phân tích xu hướng âm nhạc theo thời gian cho thấy sự gia tăng đáng kể về số lượng bài hát được phát hành từ thập niên 1950 đến 2010. Các bài hát hiện đại có xu hướng nhịp nhanh hơn, năng lượng cao hơn và độ dễ nghe cao hơn so với các bài hát trước đó, phản ánh sự thay đổi trong cấu trúc kỹ thuật và đặc trưng cảm xúc của âm nhạc qua các thập kỷ.
Về các đặc trưng kỹ thuật, các bài hát chủ yếu sử dụng điệu Major, trong khi Minor chiếm tỷ lệ nhỏ hơn. Các yếu tố như loudness, tempo có mức độ biến thiên lớn, còn danceability, liveness và speechiness phân bố tập trung, cho thấy sự ổn định về trải nghiệm cảm giác khi nghe mặc dù kỹ thuật âm nhạc đa dạng.
Các bài hát có mức độ phổ biến cao thường có danceability, energy và valence lớn, cho thấy nhịp điệu, năng lượng và cảm xúc tích cực đóng vai trò quan trọng trong việc thu hút người nghe. Tính explicit chỉ chiếm tỷ lệ nhỏ, cho thấy nội dung lời không phải yếu tố quyết định chính đến mức độ phổ biến của bài hát.
Tổng kết, bộ dữ liệu Spotify cung cấp cái nhìn hệ thống về sự phát triển của âm nhạc và các yếu tố kỹ thuật liên quan đến sự phổ biến của bài hát. Những thông tin này có giá trị trong nghiên cứu xu hướng âm nhạc, dự báo hành vi người nghe và phát triển các hệ thống đề xuất bài hát phù hợp.
Kết quả nghiên cứu cho thấy các đặc trưng kỹ thuật của bài hát, như năng lượng, nhịp điệu và cảm xúc tích cực, có ảnh hưởng rõ rệt đến mức độ phổ biến. Do đó, các cơ quan quản lý, tổ chức âm nhạc và các nền tảng phát trực tuyến có thể cân nhắc việc xây dựng các chính sách hỗ trợ sáng tác và phát hành bài hát dựa trên các đặc trưng này. Ví dụ, việc khuyến khích đào tạo nghệ sĩ về các yếu tố âm nhạc hiện đại có thể nâng cao chất lượng sản phẩm và đáp ứng thị hiếu của khán giả.
Ngoài ra, việc thu thập, chuẩn hóa và phân tích dữ liệu âm nhạc định kỳ có thể giúp các cơ quan hoạch định chính sách theo dõi xu hướng âm nhạc, phát hiện các tác động văn hóa – xã hội và dự báo nhu cầu của người nghe. Những thông tin này có thể hỗ trợ quyết định về việc cấp phép, đầu tư vào nghệ sĩ mới hoặc điều chỉnh các quy định liên quan đến bản quyền, phát hành và quảng bá nội dung âm nhạc.
Cuối cùng, nghiên cứu cũng nhấn mạnh vai trò của dữ liệu lớn trong ngành âm nhạc. Việc áp dụng phân tích dữ liệu để định hướng phát triển chính sách sẽ giúp nâng cao hiệu quả quản lý, thúc đẩy ngành âm nhạc phát triển bền vững, đồng thời tạo cơ sở khoa học cho các quyết định chiến lược liên quan đến sáng tạo, đầu tư và tiêu dùng âm nhạc.
Mặc dù nghiên cứu đã cung cấp những kết quả có giá trị về mối quan hệ giữa các yếu tố kỹ thuật của bài hát và mức độ phổ biến, nhưng vẫn tồn tại một số hạn chế cần lưu ý. Thứ nhất, dữ liệu nghiên cứu chủ yếu dựa trên các nền tảng trực tuyến và có thể không phản ánh đầy đủ hành vi của tất cả nhóm đối tượng người nghe, dẫn đến khả năng thiên lệch mẫu. Thứ hai, nghiên cứu tập trung vào các đặc trưng âm nhạc định lượng, trong khi các yếu tố định tính như xu hướng văn hóa, sở thích cá nhân, hoặc ảnh hưởng từ mạng xã hội chưa được khai thác đầy đủ. Thứ ba, phương pháp phân tích chủ yếu dựa trên mô hình hồi quy và các chỉ số phổ biến, do đó khả năng dự báo trong các bối cảnh âm nhạc thay đổi nhanh hoặc các thể loại mới vẫn còn hạn chế.
Những hạn chế này gợi ý rằng các kết quả nghiên cứu cần được áp dụng cẩn trọng và kết hợp với các nghiên cứu bổ sung để có cái nhìn toàn diện hơn về thị trường âm nhạc.
Mục tiêu chính là phân tích sức khỏe tài chính và hiệu quả
hoạt động của Ngân hàng Nông nghiệp & Phát triển Nông thôn
(Agribank) trong giai đoạn 2015–2024.
Mục tiêu thứ cấp là đánh giá xu hướng thay đổi các chỉ số tài
chính nội tại qua các năm để rút ra nhận xét về hiệu quả quản
lý ngân hàng.
Bộ dữ liệu được xây dựng từ Báo cáo tài chính hợp nhất/riêng
hàng năm đã kiểm toán của Agribank, bao gồm:
- Bảng Cân đối Kế toán
- Bảng Kết quả Kinh doanh
- Bảng Lưu chuyển tiền tệ
Phạm vi dữ liệu: từ 31/12/2015 đến 31/12/2024.
Dữ liệu đã được tổng hợp và chuyển sang Excel để thuận tiện cho phân
tích.
[1] 10 22
[1] 10
[1] 22
[1] “Số quan sát: 10”
[1] “Số biến: 22”
library(tibble)
library(knitr)
sapply(dl, class) %>%
enframe(name = "Variable", value = "Data_Type") %>%
kable(caption = "Kiểu dữ liệu của từng biến trong bộ dữ liệu dl")| Variable | Data_Type |
|---|---|
| Year | numeric |
| TTS | numeric |
| nophaitra | numeric |
| VCSH | numeric |
| tienguicuakhachhang | numeric |
| phaitranhadautuvetienguigiaodichck | numeric |
| phaitracotucgocvalaitraiphieu | numeric |
| congdoanhthuhoatdong | numeric |
| congchiphihoatdong | numeric |
| congdoanhthuhoatdongtaichinh | numeric |
| congchiphitaichinh | numeric |
| chiphiquanlycongtick | numeric |
| tongloinhuanketoantruocthue | numeric |
| chiphithuetndn | numeric |
| loinhuanketoansauthuetndn | numeric |
| tonglotoandienkhacsauthuetndn | numeric |
| laicobantrencophieu | numeric |
| luuchuyentienthuantuhoatdongkinhdoanh | numeric |
| luuchuyentienthuantuhoatdongdautu | numeric |
| luuchuyentienthuantuhoatdongtaichinh | character |
| tienvacackhoantuongduongtiendaunam | numeric |
| tienvacackhoantuongduongtiencuoinam | numeric |
if (any(is.na(dl))) {
na_summary <- data.frame(
Variable = names(dl),
Missing_Count = colSums(is.na(dl)),
Missing_Percent = round(colMeans(is.na(dl)) * 100, 2)
)
library(knitr)
kable(na_summary, caption = "Thống kê giá trị thiếu (NA) theo từng biến trong bộ dữ liệu dl")
} else {
cat("Bộ dữ liệu không có giá trị thiếu (NA).")
}Bộ dữ liệu không có giá trị thiếu (NA).
dup_count <- sum(duplicated(dl))
if (dup_count > 0) {
cat("Số lượng quan sát bị trùng lặp trong bộ dữ liệu là:", dup_count, "\n")
dl[duplicated(dl), ]
} else {
cat("Không có giá trị trùng lặp trong bộ dữ liệu.")
}Không có giá trị trùng lặp trong bộ dữ liệu.
invalid_values <- sapply(dl, function(x) sum(is.infinite(x) | is.nan(x)))
invalid_total <- sum(invalid_values)
if (invalid_total == 0) {
cat("✅ Dữ liệu hợp lệ, không có giá trị vô hạn (Inf) hoặc không xác định (NaN).")
} else {
cat("⚠️ Có", invalid_total, "giá trị không hợp lệ trong bộ dữ liệu.\n")
print(invalid_values[invalid_values > 0])
}✅ Dữ liệu hợp lệ, không có giá trị vô hạn (Inf) hoặc không xác định (NaN).
tibble [10 × 22] (S3: tbl_df/tbl/data.frame) $ Year : num [1:10] 2015 2016 2017 2018 2019 ... $ TTS : num [1:10] 2.24e+12 1.66e+12 1.78e+12 1.92e+12 2.11e+12 ... $ nophaitra : num [1:10] 1.88e+11 1.20e+10 1.28e+10 4.67e+10 1.44e+11 ... $ VCSH : num [1:10] 2.05e+12 1.65e+12 1.76e+12 1.87e+12 1.97e+12 ... $ tienguicuakhachhang : num [1:10] 1.64e+11 1.85e+11 2.74e+11 2.86e+11 1.50e+11 ... $ phaitranhadautuvetienguigiaodichck : num [1:10] 1.64e+11 1.82e+11 2.70e+11 2.83e+11 1.47e+11 ... $ phaitracotucgocvalaitraiphieu : num [1:10] 2.11e+09 3.47e+09 3.22e+09 3.23e+09 2.94e+09 ... $ congdoanhthuhoatdong : num [1:10] 1.69e+11 1.34e+11 1.85e+11 1.81e+11 2.04e+11 ... $ congchiphihoatdong : num [1:10] -5.02e+10 -4.56e+11 -4.81e+10 -2.91e+10 -4.58e+10 ... $ congdoanhthuhoatdongtaichinh : num [1:10] 0.00 1.74e+09 1.35e+09 1.29e+09 1.23e+09 ... $ congchiphitaichinh : num [1:10] 0.00 -4.71e+08 -7.57e+08 -8.18e+08 0.00 ... $ chiphiquanlycongtick : num [1:10] -3.31e+11 -4.43e+10 -5.58e+10 -6.80e+10 -7.43e+10 ... $ tongloinhuanketoantruocthue : num [1:10] -2.13e+11 -3.66e+11 8.16e+10 8.48e+10 8.53e+10 ... $ chiphithuetndn : num [1:10] 2.60e+10 -3.96e+10 -1.63e+10 -1.70e+10 -1.71e+10 ... $ loinhuanketoansauthuetndn : num [1:10] -1.87e+11 -4.05e+11 6.53e+10 6.78e+10 6.83e+10 ... $ tonglotoandienkhacsauthuetndn : num [1:10] 0.00 0.00 5.02e+10 3.88e+10 2.82e+10 ... $ laicobantrencophieu : num [1:10] -884 -1919 309 321 323 ... $ luuchuyentienthuantuhoatdongkinhdoanh: num [1:10] 3.95e+11 -2.03e+11 1.55e+10 -4.61e+11 1.27e+11 ... $ luuchuyentienthuantuhoatdongdautu : num [1:10] -3.51e+09 -1.91e+09 -4.29e+09 2.82e+11 -3.05e+09 ... $ luuchuyentienthuantuhoatdongtaichinh : chr [1:10] "-181366661000" "122802000" "1228070000" "1,400,000,000 " ... $ tienvacackhoantuongduongtiendaunam : num [1:10] 3.53e+11 3.98e+11 1.93e+11 2.04e+11 2.49e+10 ... $ tienvacackhoantuongduongtiencuoinam : num [1:10] 5.63e+11 1.93e+11 2.04e+11 2.49e+10 1.14e+11 ...
[1] “year”
[2] “tts”
[3] “nophaitra”
[4] “vcsh”
[5] “tienguicuakhachhang”
[6] “phaitranhadautuvetienguigiaodichck”
[7] “phaitracotucgocvalaitraiphieu”
[8] “congdoanhthuhoatdong”
[9] “congchiphihoatdong”
[10] “congdoanhthuhoatdongtaichinh”
[11] “congchiphitaichinh”
[12] “chiphiquanlycongtick”
[13] “tongloinhuanketoantruocthue”
[14] “chiphithuetndn”
[15] “loinhuanketoansauthuetndn”
[16] “tonglotoandienkhacsauthuetndn”
[17] “laicobantrencophieu”
[18] “luuchuyentienthuantuhoatdongkinhdoanh” [19]
“luuchuyentienthuantuhoatdongdautu”
[20] “luuchuyentienthuantuhoatdongtaichinh” [21]
“tienvacackhoantuongduongtiendaunam”
[22] “tienvacackhoantuongduongtiencuoinam”
year tts nophaitra vcsh
Min. :2015 Min. :1.660e+12 Min. :1.204e+10 Min. :1.648e+12
1st Qu.:2017 1st Qu.:1.966e+12 1st Qu.:7.109e+10 1st Qu.:1.895e+12
Median :2020 Median :2.264e+12 Median :1.890e+11 Median :2.075e+12
Mean :2020 Mean :2.407e+12 Mean :2.787e+11 Mean :2.129e+12
3rd Qu.:2022 3rd Qu.:2.789e+12 3rd Qu.:3.030e+11 3rd Qu.:2.449e+12
Max. :2024 Max. :3.472e+12 Max. :1.030e+12 Max. :2.497e+12
tienguicuakhachhang phaitranhadautuvetienguigiaodichck
Min. :1.496e+11 Min. :1.466e+11
1st Qu.:2.073e+11 1st Qu.:2.039e+11
Median :3.525e+11 Median :3.475e+11
Mean :4.549e+11 Mean :3.584e+11
3rd Qu.:5.180e+11 3rd Qu.:4.942e+11
Max. :1.410e+12 Max. :6.388e+11
phaitracotucgocvalaitraiphieu congdoanhthuhoatdong congchiphihoatdong
Min. :2.110e+09 Min. :1.336e+11 Min. :-4.558e+11
1st Qu.:3.222e+09 1st Qu.:1.821e+11 1st Qu.:-4.754e+10
Median :4.988e+09 Median :2.144e+11 Median :-2.430e+10
Mean :9.665e+10 Mean :2.633e+11 Mean :-3.351e+10
3rd Qu.:9.678e+09 3rd Qu.:3.659e+11 3rd Qu.: 5.014e+10
Max. :8.642e+11 Max. :4.129e+11 Max. : 1.237e+11
congdoanhthuhoatdongtaichinh congchiphitaichinh chiphiquanlycongtick
Min. :0.000e+00 Min. :-8.175e+08 Min. :-3.313e+11
1st Qu.:1.186e+09 1st Qu.:-3.534e+08 1st Qu.:-7.277e+10
Median :1.268e+09 Median : 0.000e+00 Median :-5.003e+10
Mean :1.188e+09 Mean : 4.594e+09 Mean :-2.627e+10
3rd Qu.:1.347e+09 3rd Qu.: 0.000e+00 3rd Qu.: 9.469e+10
Max. :1.739e+09 Max. : 3.416e+10 Max. : 1.116e+11
tongloinhuanketoantruocthue chiphithuetndn loinhuanketoansauthuetndn
Min. :-3.658e+11 Min. :-3.958e+10 Min. :-4.054e+11
1st Qu.: 8.240e+10 1st Qu.:-1.705e+10 1st Qu.: 6.591e+10
Median : 1.032e+11 Median : 4.846e+09 Median : 8.253e+10
Mean : 7.574e+10 Mean : 5.715e+09 Mean : 5.240e+10
3rd Qu.: 1.774e+11 3rd Qu.: 3.390e+10 3rd Qu.: 1.435e+11
Max. : 4.320e+11 Max. : 4.184e+10 Max. : 3.901e+11
tonglotoandienkhacsauthuetndn laicobantrencophieu
Min. :-7.261e+10 Min. :-1919.0
1st Qu.:-1.750e+10 1st Qu.: 312.0
Median : 0.000e+00 Median : 390.5
Mean : 4.804e+08 Mean : 244.4
3rd Qu.: 3.176e+10 3rd Qu.: 666.0
Max. : 5.025e+10 Max. : 1840.0
luuchuyentienthuantuhoatdongkinhdoanh luuchuyentienthuantuhoatdongdautu
Min. :-4.614e+11 Min. :-8.668e+10
1st Qu.:-2.640e+11 1st Qu.:-1.668e+10
Median : 2.186e+10 Median :-5.732e+09
Mean :-1.925e+10 Mean : 6.118e+09
3rd Qu.: 1.718e+11 3rd Qu.:-3.166e+09
Max. : 3.953e+11 Max. : 2.821e+11
luuchuyentienthuantuhoatdongtaichinh tienvacackhoantuongduongtiendaunam
Length:10 Min. :2.494e+10
Class :character 1st Qu.:1.175e+11
Mode :character Median :1.987e+11
Mean :2.265e+11
3rd Qu.:3.408e+11
Max. :5.188e+11
tienvacackhoantuongduongtiencuoinam
Min. :2.494e+10
1st Qu.:1.175e+11
Median :1.987e+11
Mean :2.532e+11
3rd Qu.:4.165e+11
Max. :5.633e+11
Nhận xét:
## Warning: NAs introduced by coercion
[1] “numeric”
vars_outlier <- c(
"nophaitra",
"tienguicuakhachhang",
"phaitracotucgocvalaitraiphieu",
"congchiphihoatdong",
"congdoanhthuhoatdongtaichinh",
"congchiphitaichinh",
"chiphiquanlycongtick",
"tongloinhuanketoantruocthue",
"loinhuanketoansauthuetndn",
"laicobantrencophieu",
"luuchuyentienthuantuhoatdongdautu",
"luuchuyentienthuantuhoatdongtaichinh"
)
replace_outlier_median_safe <- function(x, h = 1.5) {
if (!is.numeric(x)) return(x)
qs <- quantile(x, probs = c(0.25, 0.75), na.rm = TRUE)
iqr <- qs[2] - qs[1]
lower <- qs[1] - h * iqr
upper <- qs[2] + h * iqr
x[x < lower | x > upper] <- median(x, na.rm = TRUE)
return(x)
}
dl[vars_outlier] <- lapply(dl[vars_outlier], replace_outlier_median_safe)
count_outlier <- function(x, h = 1.5) {
if (!is.numeric(x)) return(NA)
qs <- quantile(x, probs = c(0.25, 0.75), na.rm = TRUE)
iqr <- qs[2] - qs[1]
lower <- qs[1] - h * iqr
upper <- qs[2] + h * iqr
sum(x < lower | x > upper, na.rm = TRUE)
}
outlier_after <- sapply(dl[vars_outlier], count_outlier)
outlier_summary <- data.frame(
Bien = names(outlier_after),
So_luong_ngoai_lai_sau = as.vector(outlier_after)
)
library(knitr)
kable(outlier_summary, col.names = c("Biến", "Số lượng ngoại lai sau xử lý"))| Biến | Số lượng ngoại lai sau xử lý |
|---|---|
| nophaitra | 1 |
| tienguicuakhachhang | 0 |
| phaitracotucgocvalaitraiphieu | 1 |
| congchiphihoatdong | 0 |
| congdoanhthuhoatdongtaichinh | 2 |
| congchiphitaichinh | 0 |
| chiphiquanlycongtick | 0 |
| tongloinhuanketoantruocthue | 0 |
| loinhuanketoansauthuetndn | 0 |
| laicobantrencophieu | 0 |
| luuchuyentienthuantuhoatdongdautu | 2 |
| luuchuyentienthuantuhoatdongtaichinh | 2 |
Sau khi xử lý outlier bằng phương pháp thay giá trị ngoại lai bằng median, hầu hết các giá trị cực đoan trong bộ dữ liệu đã được điều chỉnh. Tuy nhiên, vẫn còn tồn đọng một số outlier nhỏ (1–2 giá trị) ở một vài biến. Việc còn lại này không ảnh hưởng nghiêm trọng đến kết quả phân tích, vì các giá trị quá lệch đã được giảm thiểu tác động, đồng thời vẫn giữ được đặc điểm phân bố tổng thể của dữ liệu.
check_replace_negative <- function(df, method = "median") {
numeric_vars <- names(df)[sapply(df, is.numeric)]
result <- data.frame(
Bien = numeric_vars,
So_gia_tri_am = integer(length(numeric_vars)),
stringsAsFactors = FALSE
)
for (var in numeric_vars) {
n_neg <- sum(df[[var]] < 0, na.rm = TRUE)
result$So_gia_tri_am[result$Bien == var] <- n_neg
if (n_neg > 0) {
if (method == "median") {
med <- median(df[[var]][df[[var]] >= 0], na.rm = TRUE)
df[[var]][df[[var]] < 0] <- med
} else if (method == "zero") {
df[[var]][df[[var]] < 0] <- 0
} else {
stop("method phải là 'median' hoặc 'zero'")
}
}
}
if (all(result$So_gia_tri_am == 0)) {
cat("Không có giá trị âm bất thường trong dataset.\n")
} else {
cat("Các giá trị âm đã được xử lý.\n")
}
return(list(
data = df,
check = result
))
}
res <- check_replace_negative(dl, method = "median")Các giá trị âm đã được xử lý.
| Biến | Số giá trị âm |
|---|---|
| year | 0 |
| tts | 0 |
| nophaitra | 0 |
| vcsh | 0 |
| tienguicuakhachhang | 0 |
| phaitranhadautuvetienguigiaodichck | 0 |
| phaitracotucgocvalaitraiphieu | 0 |
| congdoanhthuhoatdong | 0 |
| congchiphihoatdong | 6 |
| congdoanhthuhoatdongtaichinh | 0 |
| congchiphitaichinh | 3 |
| chiphiquanlycongtick | 6 |
| tongloinhuanketoantruocthue | 0 |
| chiphithuetndn | 5 |
| loinhuanketoansauthuetndn | 0 |
| tonglotoandienkhacsauthuetndn | 4 |
| laicobantrencophieu | 0 |
| luuchuyentienthuantuhoatdongkinhdoanh | 4 |
| luuchuyentienthuantuhoatdongdautu | 10 |
| luuchuyentienthuantuhoatdongtaichinh | 0 |
| tienvacackhoantuongduongtiendaunam | 0 |
| tienvacackhoantuongduongtiencuoinam | 0 |
Sau khi kiểm tra và xử lý các giá trị âm bất thường trong bộ dữ liệu, hầu hết các giá trị cực đoan đã được thay thế bằng median của các giá trị hợp lý (không âm). Việc này giúp giảm thiểu tác động của các giá trị bất thường lên các thống kê mô tả và phân tích tiếp theo, đồng thời giữ được đặc điểm tổng thể của dữ liệu.
Tuy nhiên, vẫn tồn tại một số giá trị âm ở một vài biến, chẳng hạn như chi phí hoạt động, chi phí quản lý công trình, chi phí thuế TNDN, lưu chuyển tiền từ hoạt động đầu tư, v.v. Những giá trị âm này là bình thường trong bối cảnh dữ liệu tài chính và kế toán, bởi một số biến có thể ghi nhận các khoản chi trả âm, lỗ tạm tính, hoặc các giao dịch ngược dòng vốn. Do đó, sự tồn tại của những giá trị âm này không phải là bất thường về mặt logic và không ảnh hưởng nghiêm trọng đến các kết quả phân tích tổng quan.
Nhìn chung, việc xử lý các giá trị âm bất thường bằng median đã cải thiện tính ổn định của dữ liệu, giúp các thống kê như tổng, trung bình, phân bố không bị lệch quá mức. Đồng thời, các giá trị âm hợp lý vẫn được giữ nguyên, đảm bảo tính thực tế và chính xác của dữ liệu tài chính. Đây là bước quan trọng để dữ liệu sẵn sàng cho các phân tích mô tả, biểu đồ và so sánh giữa các biến, mà không làm mất đi ý nghĩa thực tế của các khoản chi, lợi nhuận hay lưu chuyển tiền.
Các biến phát sinh được tạo ra để quan sát sự thay đổi và đánh giá hiệu quả tài chính theo năm.
dl <- dl %>% arrange(year)
dl <- dl %>%
mutate(
growth_TTS = c(NA, diff(tts) / head(tts, -1)),
he_so_don_bay = tts / vcsh,
ROA = loinhuanketoansauthuetndn / tts,
ROE = loinhuanketoansauthuetndn / vcsh,
chat_luong_loi_nhuan = luuchuyentienthuantuhoatdongkinhdoanh / loinhuanketoansauthuetndn,
ty_le_loi_nhuan_bien = tongloinhuanketoantruocthue / congdoanhthuhoatdong,
chenhlech_tien = tienvacackhoantuongduongtiendaunam - tienvacackhoantuongduongtiencuoinam,
growth_dong_tien = c(NA, diff(luuchuyentienthuantuhoatdongkinhdoanh))
)
cols_show <- c("year",
"growth_TTS", "he_so_don_bay", "ROA", "ROE", "chat_luong_loi_nhuan",
"ty_le_loi_nhuan_bien",
"chenhlech_tien", "growth_dong_tien")
dl_display <- dl[, cols_show]
dl_display[ , -1] <- lapply(dl_display[ , -1], function(x) format(round(x, 4), big.mark = ",", scientific = FALSE))
kable(dl_display, col.names = cols_show)| year | growth_TTS | he_so_don_bay | ROA | ROE | chat_luong_loi_nhuan | ty_le_loi_nhuan_bien | chenhlech_tien | growth_dong_tien |
|---|---|---|---|---|---|---|---|---|
| 2015 | NA | 1.0916 | 0.0368 | 0.0402 | 4.7896 | 0.6112 | -210,388,970,856 | NA |
| 2016 | -0.2595 | 1.0073 | 0.0497 | 0.0501 | 1.9001 | 0.7721 | 204,589,497,801 | -238,457,168,604 |
| 2017 | 0.0700 | 1.0073 | 0.0367 | 0.0370 | 0.2371 | 0.4414 | -11,183,447,632 | -141,332,990,809 |
| 2018 | 0.0790 | 1.0249 | 0.0354 | 0.0363 | 2.3123 | 0.4681 | 179,317,640,528 | 141,332,990,809 |
| 2019 | 0.1013 | 1.0734 | 0.0323 | 0.0347 | 1.8583 | 0.4181 | -89,233,320,713 | -29,962,079,353 |
| 2020 | 0.0829 | 1.0905 | 0.0423 | 0.0462 | 0.2918 | 0.5382 | -13,377,674,889 | -98,601,438,926 |
| 2021 | 0.1981 | 1.1176 | 0.0301 | 0.0337 | 1.9001 | 0.2617 | 100,294,290,345 | 128,563,518,279 |
| 2022 | 0.0241 | 1.1233 | 0.0522 | 0.0586 | 2.3494 | 0.4911 | -277,130,985,702 | 187,008,065,037 |
| 2023 | 0.0918 | 1.2276 | 0.0478 | 0.0586 | 1.2765 | 0.5045 | -214,420,391,376 | -157,045,985,684 |
| 2024 | 0.1338 | 1.4215 | 0.0389 | 0.0553 | 1.1602 | 0.4082 | 64,898,917,912 | -29,962,079,353 |
library(dplyr)
library(tidyr)
library(knitr)
numeric_vars <- c(
"tts",
"nophaitra",
"vcsh",
"tienguicuakhachhang",
"phaitranhadautuvetienguigiaodichck",
"phaitracotucgocvalaitraiphieu",
"congdoanhthuhoatdong",
"congchiphihoatdong",
"congdoanhthuhoatdongtaichinh",
"congchiphitaichinh",
"chiphiquanlycongtick",
"tongloinhuanketoantruocthue",
"chiphithuetndn",
"loinhuanketoansauthuetndn",
"tonglotoandienkhacsauthuetndn",
"laicobantrencophieu",
"luuchuyentienthuantuhoatdongkinhdoanh",
"tienvacackhoantuongduongtiendaunam",
"tienvacackhoantuongduongtiencuoinam",
"ROA",
"ROE"
)
desc_table <- dl %>%
select(all_of(numeric_vars)) %>%
summarise(across(everything(),
list(
Min = ~min(., na.rm = TRUE),
Q1 = ~quantile(., 0.25, na.rm = TRUE),
Median = ~median(., na.rm = TRUE),
Mean = ~mean(., na.rm = TRUE),
Q3 = ~quantile(., 0.75, na.rm = TRUE),
Max = ~max(., na.rm = TRUE),
SD = ~sd(., na.rm = TRUE),
NA_count = ~sum(is.na(.))
), .names = "{col}_{fn}")) %>%
pivot_longer(cols = everything(),
names_to = c("Variable", "Statistic"),
names_sep = "_",
values_to = "Value") %>%
pivot_wider(names_from = Statistic, values_from = Value)## Warning: Expected 2 pieces. Additional pieces discarded in 21 rows [8, 16, 24, 32, 40,
## 48, 56, 64, 72, 80, 88, 96, 104, 112, 120, 128, 136, 144, 152, 160, ...].
| Variable | Min | Q1 | Median | Mean | Q3 | Max | SD | NA |
|---|---|---|---|---|---|---|---|---|
| tts | 1.660379e+12 | 1.965632e+12 | 2.264296e+12 | 2.407331e+12 | 2.788711e+12 | 3.472227e+12 | 5.923027e+11 | 0 |
| nophaitra | 1.204437e+10 | 7.109135e+10 | 1.885294e+11 | 1.946829e+11 | 2.636188e+11 | 5.678591e+11 | 1.672719e+11 | 0 |
| vcsh | 1.648335e+12 | 1.894540e+12 | 2.075346e+12 | 2.128589e+12 | 2.448953e+12 | 2.497221e+12 | 3.224270e+11 | 0 |
| tienguicuakhachhang | 1.496126e+11 | 2.073229e+11 | 3.192224e+11 | 3.490866e+11 | 4.705734e+11 | 6.456973e+11 | 1.682913e+11 | 0 |
| phaitranhadautuvetienguigiaodichck | 1.466160e+11 | 2.039152e+11 | 3.474821e+11 | 3.583890e+11 | 4.941751e+11 | 6.388127e+11 | 1.744514e+11 | 0 |
| phaitracotucgocvalaitraiphieu | 2.110166e+09 | 3.221594e+09 | 4.229462e+09 | 4.894996e+09 | 6.126037e+09 | 1.060884e+10 | 2.546917e+09 | 0 |
| congdoanhthuhoatdong | 1.336001e+11 | 1.821239e+11 | 2.144384e+11 | 2.633149e+11 | 3.658751e+11 | 4.128969e+11 | 1.072867e+11 | 0 |
| congchiphihoatdong | 1.648053e+10 | 8.666343e+10 | 8.666343e+10 | 8.334531e+10 | 8.666343e+10 | 1.236652e+11 | 2.880097e+10 | 0 |
| congdoanhthuhoatdongtaichinh | 1.010284e+09 | 1.233290e+09 | 1.268236e+09 | 1.267272e+09 | 1.323993e+09 | 1.502188e+09 | 1.265984e+08 | 0 |
| congchiphitaichinh | 0.000000e+00 | 0.000000e+00 | 0.000000e+00 | 0.000000e+00 | 0.000000e+00 | 0.000000e+00 | 0.000000e+00 | 0 |
| chiphiquanlycongtick | 8.149509e+10 | 1.019987e+11 | 1.019987e+11 | 1.009112e+11 | 1.019987e+11 | 1.116268e+11 | 7.588464e+09 | 0 |
| tongloinhuanketoantruocthue | 8.159409e+10 | 8.977992e+10 | 1.031564e+11 | 1.213460e+11 | 1.566557e+11 | 1.823100e+11 | 4.032831e+10 | 0 |
| chiphithuetndn | 2.601009e+10 | 3.406556e+10 | 3.406556e+10 | 3.416382e+10 | 3.406556e+10 | 4.184212e+10 | 3.792809e+09 | 0 |
| loinhuanketoansauthuetndn | 6.527527e+10 | 7.182394e+10 | 8.252509e+10 | 9.735199e+10 | 1.255624e+11 | 1.463428e+11 | 3.271667e+10 | 0 |
| tonglotoandienkhacsauthuetndn | 0.000000e+00 | 2.880644e+10 | 3.057837e+10 | 2.724987e+10 | 3.235030e+10 | 5.024653e+10 | 1.572183e+10 | 0 |
| laicobantrencophieu | 3.090000e+02 | 3.398750e+02 | 3.905000e+02 | 4.578500e+02 | 5.847500e+02 | 6.900000e+02 | 1.505876e+02 | 0 |
| luuchuyentienthuantuhoatdongkinhdoanh | 1.547385e+10 | 1.343353e+11 | 1.568068e+11 | 1.723637e+11 | 1.792784e+11 | 3.952640e+11 | 1.191231e+11 | 0 |
| tienvacackhoantuongduongtiendaunam | 2.493602e+10 | 1.175138e+11 | 1.986619e+11 | 2.264971e+11 | 3.407668e+11 | 5.188041e+11 | 1.636828e+11 | 0 |
| tienvacackhoantuongduongtiencuoinam | 2.493602e+10 | 1.175138e+11 | 1.986619e+11 | 2.531605e+11 | 4.165248e+11 | 5.632835e+11 | 1.982943e+11 | 0 |
| ROA | 3.012670e-02 | 3.571580e-02 | 3.786480e-02 | 4.022780e-02 | 4.641310e-02 | 5.216860e-02 | 7.507500e-03 | 0 |
| ROE | 3.366940e-02 | 3.644430e-02 | 4.317130e-02 | 4.506230e-02 | 5.401340e-02 | 5.864700e-02 | 1.001640e-02 | 0 |
Biến TTS dao động từ 1.66×10¹² đến 3.47×10¹², trung bình 2.41×10¹², phản ánh quy mô tổng tài sản tăng trưởng ổn định qua các năm. Độ lệch chuẩn 5.92×10¹¹ cho thấy biến động vừa phải trong quy mô tài sản.
Biến Nophaitra từ 1.20×10¹⁰ đến 5.68×10¹¹, trung bình 1.95×10¹¹, thể hiện khả năng nợ phải trả có sự dao động lớn, có thể do biến động hoạt động kinh doanh và nghĩa vụ tài chính.
Biến VCSH trung bình 2.13×10¹², dao động từ 1.65×10¹² đến 2.50×10¹², phản ánh khả năng tự chủ tài chính ổn định, độ lệch chuẩn 3.22×10¹¹ cho thấy sự thay đổi vừa phải giữa các năm.
Biến Tienguicuakhachhang trung bình 3.49×10¹¹, dao động từ 1.50×10¹¹ đến 6.46×10¹¹, thể hiện xu hướng huy động vốn tăng trưởng, độ lệch chuẩn 1.68×10¹¹ phản ánh biến động đáng kể trong hoạt động huy động.
Biến Phaitranhadautuvetienguigiaodichck trung bình 3.58×10¹¹, dao động từ 1.47×10¹¹ đến 6.39×10¹¹, cho thấy hoạt động đầu tư tích cực, đồng thời biến động lớn giữa các năm (SD = 1.74×10¹¹).
Biến Congdoanhthuhoatdong trung bình 2.63×10¹¹, dao động từ 1.34×10¹¹ đến 4.13×10¹¹, phản ánh tăng trưởng doanh thu hoạt động kinh doanh, độ lệch chuẩn 1.07×10¹¹.
Biến Congchiphihoatdong dao động từ 1.65×10¹⁰ đến 1.24×10¹¹, trung bình 8.33×10¹⁰, phản ánh chi phí hoạt động ổn định nhưng có biến động cao (SD = 2.88×10¹⁰).
Biến Loinhuanketoansauthuetndn dao động từ 6.53×10¹⁰ đến 1.46×10¹¹, trung bình 9.74×10¹⁰, cho thấy lợi nhuận sau thuế tăng trưởng nhưng có biến động đáng kể, SD 3.27×10¹⁰.
Biến Laicobantrencophieu dao động từ 309 đến 690, trung bình 457.85, phản ánh kết quả sinh lời trên cổ phiếu tương đối ổn định, SD 150.59.
Biến Luuchuyentienthuantuhoatdongkinhdoanh dao động từ 1.55×10¹⁰ đến 3.95×10¹¹, trung bình 1.72×10¹¹, thể hiện dòng tiền từ hoạt động kinh doanh biến động mạnh, SD 1.19×10¹¹.
Biến Tienvacackhoantuongduongtiendaunam và Tienvacackhoantuongduongtiencuoinam lần lượt trung bình 2.26×10¹¹ và 2.53×10¹¹, dao động tương ứng 2.49×10¹⁰ – 5.19×10¹¹ và 2.49×10¹⁰ – 5.63×10¹¹, phản ánh khối lượng tiền mặt và các khoản tương đương tăng trưởng và biến động lớn.
Biến ROA dao động từ 0.0301 đến 0.0522, trung bình 0.0402, phản ánh hiệu quả sinh lợi trên tổng tài sản ổn định với biến động vừa phải (SD = 0.0075).
Biến ROE dao động từ 0.0337 đến 0.0586, trung bình 0.0451, cho thấy khả năng sinh lợi trên vốn chủ sở hữu tăng trưởng ổn định, SD 0.0100.
Các biến tài chính quan trọng thể hiện xu hướng tăng trưởng ổn định về quy mô tài sản, vốn chủ sở hữu, doanh thu, lợi nhuận và dòng tiền, đồng thời có một số biến động đáng kể ở các chỉ số thanh khoản và dòng tiền.
cagr <- function(x) {
n <- length(x)
(last(x)/first(x))^(1/(n-1)) - 1
}
summary_table <- dl %>%
summarise(
tts_cagr = cagr(tts),
vcsh_cagr = cagr(vcsh),
lnsk_cagr = cagr(loinhuanketoansauthuetndn),
tts_sd = sd(tts, na.rm = TRUE),
vcsh_sd = sd(vcsh, na.rm = TRUE),
lnsk_sd = sd(loinhuanketoansauthuetndn, na.rm = TRUE),
roe_range = max(ROE, na.rm = TRUE) - min(ROE, na.rm = TRUE),
roa_range = max(ROA, na.rm = TRUE) - min(ROA, na.rm = TRUE),
finance_ratio_mean = mean(congchiphitaichinh / congdoanhthuhoatdong, na.rm = TRUE),
ros_mean = mean(loinhuanketoansauthuetndn / congdoanhthuhoatdong, na.rm = TRUE),
cash_ratio_mean = mean(tienvacackhoantuongduongtiencuoinam / tts, na.rm = TRUE),
effective_tax_mean = mean(chiphithuetndn / tongloinhuanketoantruocthue, na.rm = TRUE),
cor_profit_cashflow = cor(loinhuanketoansauthuetndn, luuchuyentienthuantuhoatdongkinhdoanh, use = "complete.obs"),
cor_roe_tts = cor(ROE, tts, use = "complete.obs"),
cor_tts_vcsh = cor(tts, vcsh, use = "complete.obs")
)
summary_table_long <- summary_table %>%
pivot_longer(cols = everything(), names_to = "Metric", values_to = "Value")
kable(summary_table_long, digits = 4)| Metric | Value |
|---|---|
| tts_cagr | 4.980000e-02 |
| vcsh_cagr | 1.940000e-02 |
| lnsk_cagr | 5.630000e-02 |
| tts_sd | 5.923027e+11 |
| vcsh_sd | 3.224270e+11 |
| lnsk_sd | 3.271667e+10 |
| roe_range | 2.500000e-02 |
| roa_range | 2.200000e-02 |
| finance_ratio_mean | 0.000000e+00 |
| ros_mean | 3.939000e-01 |
| cash_ratio_mean | 1.024000e-01 |
| effective_tax_mean | 3.072000e-01 |
| cor_profit_cashflow | 3.584000e-01 |
| cor_roe_tts | 5.776000e-01 |
| cor_tts_vcsh | 9.352000e-01 |
###Nhận xét
Các chỉ tiêu tổng tài sản (TTS), vốn chủ sở hữu (VCSH) và lợi nhuận sau thuế (LNST) đều có mức tăng trưởng trung bình dương, lần lượt là 4,98%, 1,94% và 5,63% mỗi năm, phản ánh sự mở rộng tổng thể của hoạt động doanh nghiệp trong giai đoạn nghiên cứu. Độ lệch chuẩn của TTS (5,92×10¹¹) và VCSH (3,22×10¹¹) cho thấy tổng tài sản biến động mạnh hơn vốn chủ sở hữu, trong khi độ lệch chuẩn của LNST (3,27×10¹⁰) thấp hơn, cho thấy lợi nhuận ổn định hơn so với quy mô vốn.
Biên độ ROE và ROA lần lượt là 2,5% và 2,2%, cho thấy hiệu quả sử dụng vốn và tài sản không có biến động quá lớn giữa các năm. Tỷ lệ chi phí tài chính trên doanh thu gần như bằng 0, phản ánh chi phí tài chính không đáng kể. Tỷ lệ lợi nhuận ròng/doanh thu (ROS) trung bình đạt 39,4%, thể hiện hiệu quả sinh lợi cao so với doanh thu. Tỷ lệ tiền cuối năm/tổng tài sản trung bình là 10,24%, cho thấy doanh nghiệp duy trì mức thanh khoản tương đối an toàn. Thuế suất thực tế trung bình là 30,7%, tương đối phù hợp với mức thuế suất doanh nghiệp áp dụng tại Việt Nam.
Các hệ số tương quan cũng cung cấp thông tin quan trọng: lợi nhuận và dòng tiền hoạt động có tương quan vừa phải (0,358), ROE và TTS có tương quan trung bình (0,578), trong khi TTS và VCSH tương quan rất cao (0,935), phản ánh mức độ sử dụng đòn bẩy tài chính ổn định.
Nhìn chung, các chỉ tiêu tài chính cho thấy doanh nghiệp tăng trưởng ổn định, lợi nhuận duy trì ở mức cao và quản lý dòng tiền hiệu quả, đồng thời sử dụng vốn chủ sở hữu và tổng tài sản một cách cân đối, minh chứng cho chiến lược tài chính ổn định và bền vững.
vars_fin <- c(
"tts",
"vcsh",
"loinhuanketoansauthuetndn",
"congdoanhthuhoatdong",
"congchiphihoatdong",
"tongloinhuanketoantruocthue",
"chiphithuetndn",
"laicobantrencophieu",
"luuchuyentienthuantuhoatdongkinhdoanh",
"tienvacackhoantuongduongtiendaunam",
"tienvacackhoantuongduongtiencuoinam",
"ROA",
"ROE"
)
cor_matrix <- dl %>%
select(all_of(vars_fin)) %>%
cor(use = "complete.obs")
kable(round(cor_matrix, 2))| tts | vcsh | loinhuanketoansauthuetndn | congdoanhthuhoatdong | congchiphihoatdong | tongloinhuanketoantruocthue | chiphithuetndn | laicobantrencophieu | luuchuyentienthuantuhoatdongkinhdoanh | tienvacackhoantuongduongtiendaunam | tienvacackhoantuongduongtiencuoinam | ROA | ROE | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| tts | 1.00 | 0.94 | 0.82 | 0.93 | 0.01 | 0.83 | 0.22 | 0.82 | 0.26 | 0.24 | 0.50 | 0.13 | 0.58 |
| vcsh | 0.94 | 1.00 | 0.79 | 0.94 | -0.12 | 0.79 | 0.33 | 0.79 | 0.35 | -0.05 | 0.37 | 0.13 | 0.49 |
| loinhuanketoansauthuetndn | 0.82 | 0.79 | 1.00 | 0.75 | 0.30 | 1.00 | 0.07 | 1.00 | 0.36 | 0.20 | 0.60 | 0.66 | 0.92 |
| congdoanhthuhoatdong | 0.93 | 0.94 | 0.75 | 1.00 | -0.14 | 0.75 | 0.51 | 0.75 | 0.15 | 0.03 | 0.24 | 0.09 | 0.47 |
| congchiphihoatdong | 0.01 | -0.12 | 0.30 | -0.14 | 1.00 | 0.30 | -0.62 | 0.31 | 0.22 | 0.12 | 0.31 | 0.48 | 0.47 |
| tongloinhuanketoantruocthue | 0.83 | 0.79 | 1.00 | 0.75 | 0.30 | 1.00 | 0.07 | 1.00 | 0.35 | 0.21 | 0.61 | 0.65 | 0.92 |
| chiphithuetndn | 0.22 | 0.33 | 0.07 | 0.51 | -0.62 | 0.07 | 1.00 | 0.06 | -0.46 | -0.33 | -0.57 | -0.14 | -0.09 |
| laicobantrencophieu | 0.82 | 0.79 | 1.00 | 0.75 | 0.31 | 1.00 | 0.06 | 1.00 | 0.37 | 0.19 | 0.60 | 0.66 | 0.92 |
| luuchuyentienthuantuhoatdongkinhdoanh | 0.26 | 0.35 | 0.36 | 0.15 | 0.22 | 0.35 | -0.46 | 0.37 | 1.00 | 0.12 | 0.56 | 0.26 | 0.29 |
| tienvacackhoantuongduongtiendaunam | 0.24 | -0.05 | 0.20 | 0.03 | 0.12 | 0.21 | -0.33 | 0.19 | 0.12 | 1.00 | 0.58 | 0.14 | 0.35 |
| tienvacackhoantuongduongtiencuoinam | 0.50 | 0.37 | 0.60 | 0.24 | 0.31 | 0.61 | -0.57 | 0.60 | 0.56 | 0.58 | 1.00 | 0.38 | 0.61 |
| ROA | 0.13 | 0.13 | 0.66 | 0.09 | 0.48 | 0.65 | -0.14 | 0.66 | 0.26 | 0.14 | 0.38 | 1.00 | 0.86 |
| ROE | 0.58 | 0.49 | 0.92 | 0.47 | 0.47 | 0.92 | -0.09 | 0.92 | 0.29 | 0.35 | 0.61 | 0.86 | 1.00 |
###Nhận xét - Tổng tài sản (TTS) và vốn chủ sở hữu (VCSH) có tương quan rất cao (0.94), cho thấy vốn chủ sở hữu tăng gần tương ứng với tổng tài sản, phản ánh mức độ đòn bẩy ổn định.
Lợi nhuận sau thuế (LN SK) tương quan mạnh với TTS (0.82), VCSH (0.79) và ROE (0.92), phản ánh lợi nhuận chủ yếu phụ thuộc vào vốn bỏ ra và hiệu quả sử dụng vốn.
Doanh thu hoạt động (DOHD) tương quan cao với TTS (0.93) và VCSH (0.94), nghĩa là quy mô tài sản và vốn ảnh hưởng lớn tới doanh thu.
Chi phí hoạt động (CPHD) gần như không liên quan tới TTS (0.01) và có tương quan âm với VCSH (-0.12), cho thấy chi phí hoạt động không tăng tỉ lệ thuận với tổng tài sản hay vốn.
Tổng lợi nhuận trước thuế (LNTT) và LN SK đều có tương quan rất cao với nhau (≈1.00) và với TTS/VCSH/DOHD, phản ánh chi phí thuế ít làm thay đổi xu hướng lợi nhuận.
Chi phí thuế TNDN có tương quan âm với dòng tiền hoạt động và tiền cuối năm, gợi ý chi phí thuế cao có thể làm giảm thanh khoản.
Lưu chuyển tiền thuần từ HĐKD có tương quan vừa phải với lợi nhuận sau thuế (0.36), cho thấy doanh thu thực tế tạo dòng tiền nhưng không hoàn toàn quyết định lợi nhuận.
Tiền và các khoản tương đương đầu năm/cuối năm có tương quan cao với nhau (0.58–0.61) và vừa phải với LN SK, ROE, ROA, cho thấy vốn lưu động tăng theo lợi nhuận và hiệu quả sử dụng vốn.
ROA và ROE có tương quan cao (0.86), phù hợp lý thuyết: ROE chịu ảnh hưởng trực tiếp từ ROA và đòn bẩy tài chính.
Các chỉ tiêu liên quan tới lợi nhuận cơ bản trên cổ phiếu, EPS có tương quan mạnh với LN SK, ROE, ROA, phản ánh giá trị cổ đông gắn với hiệu quả kinh doanh.
###Tóm lại: Ma trận tương quan cho thấy vốn và tổng tài sản quyết định doanh thu, lợi nhuận, trong khi dòng tiền và chi phí tài chính ảnh hưởng vừa phải tới lợi nhuận. ROE, ROA phản ánh chính xác hiệu quả sử dụng vốn, các chỉ tiêu liên quan tới cổ đông (EPS, LNST) theo sát lợi nhuận.
library(dplyr)
library(knitr)
dl_cash <- dl %>%
mutate(
cash_ratio = tienvacackhoantuongduongtiencuoinam / tts,
cash_growth = (tienvacackhoantuongduongtiencuoinam - tienvacackhoantuongduongtiendaunam) / tienvacackhoantuongduongtiendaunam
) %>%
select(tienvacackhoantuongduongtiendaunam, tienvacackhoantuongduongtiencuoinam,
luuchuyentienthuantuhoatdongkinhdoanh, cash_ratio, cash_growth)
kable(dl_cash, digits = 3)| tienvacackhoantuongduongtiendaunam | tienvacackhoantuongduongtiencuoinam | luuchuyentienthuantuhoatdongkinhdoanh | cash_ratio | cash_growth |
|---|---|---|---|---|
| 352894496064 | 563283466920 | 395264009771 | 0.251 | 0.596 |
| 397659713908 | 193070216107 | 156806841168 | 0.116 | -0.514 |
| 193070216107 | 204253663739 | 15473850359 | 0.115 | 0.058 |
| 204253663739 | 24936023211 | 156806841168 | 0.013 | -0.878 |
| 24936023211 | 114169343924 | 126844761815 | 0.054 | 3.578 |
| 114169343924 | 127547018813 | 28243322889 | 0.056 | 0.117 |
| 127547018813 | 27252728468 | 156806841168 | 0.010 | -0.786 |
| 27252728468 | 304383714170 | 343814906204 | 0.109 | 10.169 |
| 304383714170 | 518804105546 | 186768920520 | 0.169 | 0.704 |
| 518804105546 | 453905187634 | 156806841168 | 0.131 | -0.125 |
###Nhận xét: Dữ liệu dòng tiền và khả năng thanh toán cho thấy một số đặc điểm nổi bật:
Tổng quan dòng tiền: Dòng tiền từ hoạt động kinh doanh (luuchuyentienthuantuhoatdongkinhdoanh) dao động khá lớn, từ 15,4 tỷ đến gần 395 tỷ, phản ánh sự biến động mạnh của hoạt động vận hành. Một số năm ghi nhận dòng tiền âm so với tổng tài sản, có khả năng do chi phí hoặc đầu tư lớn.
Khả năng thanh toán: Tỷ lệ tiền mặt cuối năm trên tổng tài sản (cash_ratio) tương đối thấp, phổ biến dưới 15%, chỉ có năm đầu tiên là 25%. Điều này cho thấy doanh nghiệp duy trì một phần tiền mặt hạn chế so với tổng tài sản, có thể tập trung vốn vào đầu tư hoặc các tài sản khác.
Tăng trưởng tiền mặt: Tăng trưởng tiền mặt (cash_growth) biến động mạnh, từ -87,8% đến 10,169 lần. Một số năm có mức tăng trưởng cực đoan (ví dụ 10,169 lần) phản ánh sự thay đổi đột ngột trong lượng tiền mặt, có thể do nhập vốn lớn hoặc thoái vốn, cần xem xét nguyên nhân chi tiết.
###Nhận xét chung: Do sự biến động dòng tiền và khả năng thanh toán không đồng đều, doanh nghiệp có thể gặp rủi ro thanh khoản trong một số năm. Tuy nhiên, các năm có dòng tiền dương mạnh và tỷ lệ tiền mặt hợp lý cho thấy doanh nghiệp vẫn duy trì khả năng đáp ứng nghĩa vụ ngắn hạn nếu quản lý tốt.
df_efficiency <- dl %>%
select(tts, vcsh, loinhuanketoansauthuetndn, ROA, ROE)
efficiency_summary <- df_efficiency %>%
summarise(
ROA_mean = mean(ROA),
ROA_min = min(ROA),
ROA_max = max(ROA),
ROA_sd = sd(ROA),
ROE_mean = mean(ROE),
ROE_min = min(ROE),
ROE_max = max(ROE),
ROE_sd = sd(ROE),
tts_mean = mean(tts),
vcsh_mean = mean(vcsh),
profit_mean = mean(loinhuanketoansauthuetndn)
)
cagr <- function(x) {
(last(x)/first(x))^(1/(length(x)-1)) - 1
}
efficiency_cagr <- df_efficiency %>%
summarise(
tts_cagr = cagr(tts),
vcsh_cagr = cagr(vcsh),
profit_cagr = cagr(loinhuanketoansauthuetndn)
)
efficiency_table <- bind_cols(efficiency_summary, efficiency_cagr)
kable(efficiency_table, digits = 4, format = "markdown",
col.names = c("ROA_mean", "ROA_min", "ROA_max", "ROA_sd",
"ROE_mean", "ROE_min", "ROE_max", "ROE_sd",
"TTS_mean", "VCSH_mean", "Profit_mean",
"TTS_CAGR", "VCSH_CAGR", "Profit_CAGR"))| ROA_mean | ROA_min | ROA_max | ROA_sd | ROE_mean | ROE_min | ROE_max | ROE_sd | TTS_mean | VCSH_mean | Profit_mean | TTS_CAGR | VCSH_CAGR | Profit_CAGR |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0.0402 | 0.0301 | 0.0522 | 0.0075 | 0.0451 | 0.0337 | 0.0586 | 0.01 | 2.407331e+12 | 2.128589e+12 | 97351986529 | 0.0498 | 0.0194 | 0.0563 |
###Nhận xét - ROA (Lợi nhuận trên tổng tài sản): Giá trị trung bình ROA là 4.02%, dao động từ 3.01% đến 5.22%, với độ lệch chuẩn 0.75%. Điều này cho thấy hiệu quả sử dụng tổng tài sản của các doanh nghiệp trong mẫu nghiên cứu khá ổn định, biến động không quá lớn.
ROE (Lợi nhuận trên vốn chủ sở hữu): ROE trung bình là 4.51%, với khoảng dao động từ 3.37% đến 5.86% và độ lệch chuẩn 1.0%. Biến động ROE lớn hơn ROA, phản ánh sự ảnh hưởng của cơ cấu vốn (đòn bẩy tài chính) đến lợi nhuận trên vốn chủ sở hữu.
Tổng tài sản (TTS) và vốn chủ sở hữu (VCSH): TTS trung bình đạt khoảng 2,41 nghìn tỷ đồng, VCSH trung bình khoảng 2,13 nghìn tỷ đồng. Điều này cho thấy các doanh nghiệp sử dụng vốn chủ sở hữu ở mức vừa phải so với tổng tài sản, phù hợp với mức đòn bẩy trung bình.
Lợi nhuận sau thuế (Profit_mean): Trung bình khoảng 97,35 tỷ đồng. Mức lợi nhuận này phản ánh khả năng sinh lời thực tế trên quy mô tài sản và vốn chủ sở hữu của doanh nghiệp.
Tăng trưởng trung bình năm (CAGR): TTS tăng trưởng trung bình 4.98%/năm, VCSH tăng trưởng 1.94%/năm, lợi nhuận sau thuế tăng 5.63%/năm. Nhìn chung, doanh nghiệp duy trì tốc độ tăng trưởng lợi nhuận cao hơn tăng trưởng vốn, điều này thể hiện khả năng cải thiện hiệu quả sử dụng vốn qua các năm.
Nhận xét tổng thể: Doanh nghiệp trong mẫu nghiên cứu có khả năng sinh lời và hiệu quả sử dụng vốn/tài sản ổn định, ROA biến động thấp hơn ROE, cho thấy đòn bẩy tài chính tác động đáng kể đến lợi nhuận trên vốn chủ sở hữu. Tăng trưởng lợi nhuận cao hơn tăng trưởng vốn chủ sở hữu chứng tỏ hiệu quả quản lý vốn và tài sản khá tốt.
financial_leverage <- dl %>%
summarise(
debt_equity_ratio = mean((tts - vcsh) / vcsh, na.rm = TRUE),
debt_equity_min = min((tts - vcsh) / vcsh, na.rm = TRUE),
debt_equity_max = max((tts - vcsh) / vcsh, na.rm = TRUE),
debt_equity_sd = sd((tts - vcsh) / vcsh, na.rm = TRUE)
)
kable(financial_leverage, digits = 3)| debt_equity_ratio | debt_equity_min | debt_equity_max | debt_equity_sd |
|---|---|---|---|
| 0.119 | 0.007 | 0.421 | 0.125 |
Cổ phiếu ngân hàng Agribank cho thấy tỷ lệ đòn bẩy tài chính trung bình là 0,119, tức phần nợ chỉ chiếm khoảng 11,9% so với vốn chủ sở hữu, phản ánh ngân hàng chủ yếu tài trợ bằng vốn tự có. Giá trị tối thiểu 0,007 cho thấy có những giai đoạn gần như không sử dụng nợ, trong khi tối đa 0,421 cho thấy ngân hàng đôi lúc tăng vay để tài trợ hoạt động, nhưng vẫn trong mức kiểm soát. Độ lệch chuẩn 0,125 chỉ ra rằng mức biến động của đòn bẩy là vừa phải, cấu trúc vốn của Agribank khá ổn định, hạn chế rủi ro tài chính.
profit_vars <- c("tongloinhuanketoantruocthue", "loinhuanketoansauthuetndn")
risk_analysis <- dl %>%
select(all_of(profit_vars)) %>%
summarise(across(everything(),
list(mean = ~mean(.x, na.rm = TRUE),
sd = ~sd(.x, na.rm = TRUE),
cv = ~sd(.x, na.rm = TRUE)/mean(.x, na.rm = TRUE)))) %>%
pivot_longer(everything(),
names_to = c("metric", "stat"),
names_sep = "_",
values_to = "value") %>%
pivot_wider(names_from = stat, values_from = value)
kable(risk_analysis, digits = 4)| metric | mean | sd | cv |
|---|---|---|---|
| tongloinhuanketoantruocthue | 121345957617 | 40328312908 | 0.3323 |
| loinhuanketoansauthuetndn | 97351986529 | 32716668615 | 0.3361 |
###Nhận xét:
finance_vars <- dl %>%
select(year, tts, vcsh, loinhuanketoansauthuetndn, congdoanhthuhoatdong,
congchiphihoatdong, tongloinhuanketoantruocthue, chiphithuetndn,
laicobantrencophieu, ROA, ROE)
finance_summary <- finance_vars %>%
group_by(year) %>%
summarise(
TTS = sum(tts, na.rm = TRUE),
VCSH = sum(vcsh, na.rm = TRUE),
LNST = sum(loinhuanketoansauthuetndn, na.rm = TRUE),
DoanhThu = sum(congdoanhthuhoatdong, na.rm = TRUE),
ChiPhi = sum(congchiphihoatdong, na.rm = TRUE),
LTKTruocThue = sum(tongloinhuanketoantruocthue, na.rm = TRUE),
CPThue = sum(chiphithuetndn, na.rm = TRUE),
LCB = sum(laicobantrencophieu, na.rm = TRUE),
ROA = mean(ROA, na.rm = TRUE),
ROE = mean(ROE, na.rm = TRUE)
)
kable(finance_summary, digits = 2)| year | TTS | VCSH | LNST | DoanhThu | ChiPhi | LTKTruocThue | CPThue | LCB | ROA | ROE |
|---|---|---|---|---|---|---|---|---|---|---|
| 2015 | 2.242172e+12 | 2.054063e+12 | 82525087441 | 168786511613 | 86663425631 | 103156359302 | 26010087063 | 390.5 | 0.04 | 0.04 |
| 2016 | 1.660379e+12 | 1.648335e+12 | 82525087441 | 133600086761 | 86663425631 | 103156359302 | 34065564109 | 390.5 | 0.05 | 0.05 |
| 2017 | 1.776671e+12 | 1.763858e+12 | 65275274846 | 184849879214 | 86663425631 | 81594093557 | 34065564109 | 309.0 | 0.04 | 0.04 |
| 2018 | 1.917072e+12 | 1.870422e+12 | 67815231463 | 181215198157 | 86663425631 | 84821567306 | 34065564109 | 321.0 | 0.04 | 0.04 |
| 2019 | 2.111309e+12 | 1.966895e+12 | 68256885192 | 204080452867 | 86663425631 | 85321106490 | 34065564109 | 323.0 | 0.03 | 0.03 |
| 2020 | 2.286421e+12 | 2.096629e+12 | 96793289690 | 224796382907 | 86663425631 | 120991612113 | 34065564109 | 458.0 | 0.04 | 0.05 |
| 2021 | 2.739269e+12 | 2.451041e+12 | 82525087441 | 394171751184 | 16480534320 | 103156359302 | 41842117681 | 390.5 | 0.03 | 0.03 |
| 2022 | 2.805191e+12 | 2.497221e+12 | 146342776418 | 367374466987 | 123665182999 | 180408340527 | 34065564109 | 690.0 | 0.05 | 0.06 |
| 2023 | 3.062598e+12 | 2.494739e+12 | 146309079421 | 361377046270 | 61357373934 | 182309995663 | 36000916242 | 679.0 | 0.05 | 0.06 |
| 2024 | 3.472227e+12 | 2.442688e+12 | 135152065932 | 412896892995 | 111969477327 | 168543782608 | 33391716676 | 627.0 | 0.04 | 0.06 |
###Nhận xét:
Nhìn vào bảng các chỉ tiêu tài chính của Agribank từ 2015 đến 2024, có thể rút ra một số nhận xét trực quan như sau:
Tổng tài sản (TTS) và vốn chủ sở hữu (VCSH) có xu hướng tăng đều qua các năm, cho thấy ngân hàng liên tục mở rộng quy mô tài sản và tăng vốn tự có. Tuy nhiên, tốc độ tăng không đồng đều, đặc biệt TTS tăng mạnh từ 2020 trở đi.
Lợi nhuận sau thuế (LNST) dao động, năm 2020 và 2022 đạt mức cao nhất trong giai đoạn này, trong khi các năm khác thấp hơn, phản ánh biến động trong hiệu quả kinh doanh.
Doanh thu hoạt động nhìn chung tăng, nhưng chi phí hoạt động (ChiPhi) vẫn giữ mức tương đối ổn định, dẫn đến lợi nhuận kế toán trước thuế (LTKTruocThue) biến động theo lợi nhuận thuần.
Chi phí thuế TNDN (CPThue) và lãi cơ bản trên cổ phiếu (LCB) cũng dao động, phản ánh sự thay đổi trong lợi nhuận và cơ cấu vốn.
ROA ổn định quanh 0.03–0.05, trong khi ROE dao động từ 0.03 đến 0.06, cho thấy hiệu quả sử dụng vốn cổ đông không quá cao nhưng duy trì ổn định.
Một số năm có giá trị LCB bất thường (ví dụ năm 2015, 2016, 2021 là 390–390.5), có thể là do cách tính lãi cơ bản hoặc điều chỉnh số liệu kế toán.
Tổng quan, Agribank duy trì tăng trưởng tài sản và vốn chủ sở hữu ổn định, nhưng biến động lợi nhuận và ROE cho thấy hiệu quả kinh doanh có lúc chưa đồng đều, cần lưu ý trong các quyết định quản lý vốn và rủi ro.
summary_table <- tibble(
Chi_tieu = c("ROA", "ROE", "Lợi nhuận sau thuế"),
Gia_tri_lon_nhat = c(scales::percent(max(dl$ROA, na.rm = TRUE)),
scales::percent(max(dl$ROE, na.rm = TRUE)),
scales::dollar(max(dl$loinhuanketoansauthuetndn, na.rm = TRUE))),
Nam_lon_nhat = c(dl$year[which.max(dl$ROA)],
dl$year[which.max(dl$ROE)],
dl$year[which.max(dl$loinhuanketoansauthuetndn)]),
Gia_tri_nho_nhat = c(scales::percent(min(dl$ROA, na.rm = TRUE)),
scales::percent(min(dl$ROE, na.rm = TRUE)),
scales::dollar(min(dl$loinhuanketoansauthuetndn, na.rm = TRUE))),
Nam_nho_nhat = c(dl$year[which.min(dl$ROA)],
dl$year[which.min(dl$ROE)],
dl$year[which.min(dl$loinhuanketoansauthuetndn)])
)
# Hiển thị bảng đẹp trong R Markdown
kable(summary_table,
col.names = c("Chỉ tiêu", "Giá trị lớn nhất", "Năm lớn nhất",
"Giá trị nhỏ nhất", "Năm nhỏ nhất"),
align = "c")| Chỉ tiêu | Giá trị lớn nhất | Năm lớn nhất | Giá trị nhỏ nhất | Năm nhỏ nhất |
|---|---|---|---|---|
| ROA | 5% | 2022 | 3% | 2021 |
| ROE | 6% | 2023 | 3% | 2021 |
| Lợi nhuận sau thuế | $146,342,776,418 | 2022 | $65,275,274,846 | 2017 |
ROA (Tỷ suất lợi nhuận trên tài sản):
Năm 2022 đạt mức cao nhất 5,22%, phản ánh hiệu quả sử dụng tài sản trong năm này tốt nhất so với các năm khác.
Năm 2021 là thấp nhất 3,01%, cho thấy hiệu quả quản lý tài sản kém hơn.
ROE (Tỷ suất lợi nhuận trên vốn chủ sở hữu):
Năm 2023 đạt cao nhất 5,86%, tức là khả năng sinh lời trên vốn chủ sở hữu mạnh nhất.
Năm 2021 cũng là thấp nhất (3,37%), tương tự ROA, cho thấy năm này ngân hàng ít hiệu quả cả về tài sản lẫn vốn chủ sở hữu.
Lợi nhuận sau thuế:
Năm 2022 đạt mức cao nhất gần 146,3 tỷ đồng, gợi ý năm này ngân hàng có kết quả kinh doanh tốt nhất.
Năm 2017 thấp nhất chỉ khoảng 65,3 tỷ đồng, cho thấy năm này lợi nhuận chưa cao, có thể do doanh thu thấp hoặc chi phí lớn.
Nhận xét tổng quát:
Năm 2021 là năm cả ROA lẫn ROE đều thấp, trong khi năm 2022 và 2023 là những năm hiệu quả kinh doanh nổi bật.
Biểu hiện này phản ánh sự tăng trưởng dần của hiệu quả sử dụng vốn và tài sản trong các năm gần đây, nhưng vẫn có biến động đáng kể.
dl_rel <- dl %>%
select(
loinhuanketoansauthuetndn,
luuchuyentienthuantuhoatdongkinhdoanh,
vcsh
)
cor_matrix <- round(cor(dl_rel, use = "complete.obs"), 3)
cor_df <- as.data.frame(cor_matrix)
cor_df <- cbind(Biến = rownames(cor_df), cor_df)
kable(cor_df, caption = "Ma trận tương quan giữa LNST, Dòng tiền HĐKD và Vốn chủ sở hữu")| Biến | loinhuanketoansauthuetndn | luuchuyentienthuantuhoatdongkinhdoanh | vcsh | |
|---|---|---|---|---|
| loinhuanketoansauthuetndn | loinhuanketoansauthuetndn | 1.000 | 0.358 | 0.794 |
| luuchuyentienthuantuhoatdongkinhdoanh | luuchuyentienthuantuhoatdongkinhdoanh | 0.358 | 1.000 | 0.355 |
| vcsh | vcsh | 0.794 | 0.355 | 1.000 |
Nhìn vào ma trận tương quan này, có thể nhận xét như sau:
Lợi nhuận sau thuế – Vốn chủ sở hữu (0.794): Có mối tương quan khá mạnh, nghĩa là khi vốn chủ sở hữu tăng thì lợi nhuận sau thuế cũng có xu hướng tăng, hoặc ngược lại. Điều này phản ánh rằng quy mô vốn của ngân hàng liên quan chặt chẽ đến khả năng sinh lợi.
Lợi nhuận sau thuế – Dòng tiền HĐKD (0.358): Tương quan ở mức vừa phải, nghĩa là lợi nhuận và dòng tiền hoạt động kinh doanh có liên quan nhưng không hoàn toàn đồng biến. Có thể có các yếu tố khác (chi phí, nợ, đầu tư) ảnh hưởng đến dòng tiền mà không phản ánh trực tiếp lên lợi nhuận.
Dòng tiền HĐKD – Vốn chủ sở hữu (0.355): Tương quan thấp, cho thấy dòng tiền hoạt động kinh doanh không quá phụ thuộc trực tiếp vào quy mô vốn chủ sở hữu, tức ngân hàng vẫn có thể tạo dòng tiền từ hoạt động kinh doanh mà không cần tăng vốn ngay lập tức.
Tóm lại: Lợi nhuận sau thuế liên quan chặt với vốn chủ sở hữu, còn dòng tiền HĐKD có ảnh hưởng nhưng mức độ thấp hơn, phản ánh rằng ngân hàng có quản lý vốn và dòng tiền tương đối độc lập trong hoạt động.
##
## Attaching package: 'scales'
## The following object is masked from 'package:purrr':
##
## discard
## The following object is masked from 'package:readr':
##
## col_factor
##
## Attaching package: 'kableExtra'
## The following object is masked from 'package:dplyr':
##
## group_rows
top_cv <- dl %>%
select(year, congdoanhthuhoatdong, congchiphihoatdong,
loinhuanketoansauthuetndn, laicobantrencophieu, tts) %>%
summarise(across(everything(), list(mean = mean, sd = sd))) %>%
pivot_longer(cols = everything(),
names_to = c("Chi_tieu", ".value"),
names_sep = "_") %>%
mutate(CV = sd / mean) %>%
arrange(desc(CV)) %>%
slice(1:5)
top_cv_table <- top_cv %>%
mutate(
mean = scales::dollar(mean),
sd = scales::dollar(sd),
CV = scales::percent(CV, accuracy = 0.1)
) %>%
rename(
"Chỉ tiêu" = Chi_tieu,
"Giá trị trung bình" = mean,
"Độ lệch chuẩn" = sd,
"Hệ số biến động" = CV
)
top_cv_table %>%
kable(caption = "Top 5 chỉ tiêu có biến động lớn nhất") %>%
kable_styling(full_width = F, position = "center", bootstrap_options = c("striped", "hover")) %>%
row_spec(0, bold = TRUE, background = "#D3D3D3") | Chỉ tiêu | Giá trị trung bình | Độ lệch chuẩn | Hệ số biến động |
|---|---|---|---|
| congdoanhthuhoatdong | $263,314,866,896 | $107,286,703,921 | 40.7% |
| congchiphihoatdong | $83,345,312,236 | $28,800,968,388 | 34.6% |
| loinhuanketoansauthuetndn | $97,351,986,528 | $32,716,668,615 | 33.6% |
| laicobantrencophieu | $458 | $151 | 32.9% |
| tts | $2,407,331,036,071 | $592,302,675,110 | 24.6% |
Bảng trên cho thấy 5 chỉ tiêu tài chính có biến động lớn nhất trong giai đoạn nghiên cứu. Cụ thể, doanh thu hoạt động có hệ số biến động cao nhất (CV = 40,7%), cho thấy doanh thu thay đổi đáng kể giữa các năm, phản ánh sự dao động trong kết quả kinh doanh hoặc tác động của các yếu tố thị trường. Chi phí hoạt động (CV = 34,6%) và lợi nhuận sau thuế (CV = 33,6%) cũng biến động mạnh, tương ứng với biến động doanh thu, cho thấy khả năng sinh lời của doanh nghiệp chịu ảnh hưởng trực tiếp từ mức doanh thu và chi phí.
Hệ số biến động của lãi cơ bản trên cổ phiếu (EPS) là 32,9%, phản ánh sự biến động lợi nhuận phân bổ trên mỗi cổ phần, điều này có thể tác động đến đánh giá cổ đông và quyết định đầu tư. Trong khi đó, tổng tài sản (TTS) có CV thấp hơn (24,6%), cho thấy quy mô tài sản của doanh nghiệp ổn định hơn so với các chỉ tiêu kết quả kinh doanh, phản ánh tính chất dài hạn của tài sản so với các chỉ tiêu thu nhập và chi phí.
Nhìn chung, các chỉ tiêu biến động mạnh chủ yếu liên quan đến doanh thu, chi phí và lợi nhuận, là những yếu tố trực tiếp quyết định kết quả kinh doanh. Việc phân tích những biến động này giúp nhận diện rủi ro và cơ hội trong hoạt động tài chính của doanh nghiệp.
library(ggplot2)
# Boxplot
dl %>%
select(all_of(numeric_vars)) %>%
pivot_longer(cols = everything(), names_to = "Variable", values_to = "Value") %>%
ggplot(aes(x = Variable, y = Value)) +
geom_boxplot(fill = "lightblue", outlier.color = "red") +
coord_flip() +
labs(title = "Boxplot các chỉ tiêu tài chính",
x = "Biến",
y = "Giá trị") +
theme_minimal() +
theme(
axis.text.y = element_text(size = 8),
axis.text.x = element_text(size = 8),
plot.title = element_text(size = 14, face = "bold")
)dl %>%
select(all_of(numeric_vars)) %>%
pivot_longer(cols = everything(), names_to = "Variable", values_to = "Value") %>%
ggplot(aes(x = Variable, y = Value)) +
geom_boxplot(fill = "lightblue", outlier.color = "red") +
coord_flip() +
scale_y_log10(labels = scales::comma) + # trục log, gọn chữ
labs(title = "Boxplot các chỉ tiêu tài chính (log scale)",
x = "Biến",
y = "Giá trị (log scale)") +
theme_minimal() +
theme(
axis.text.y = element_text(size = 8),
axis.text.x = element_text(size = 8),
plot.title = element_text(size = 14, face = "bold")
)## Warning in scale_y_log10(labels = scales::comma): log-10 transformation
## introduced infinite values.
## Warning: Removed 12 rows containing non-finite outside the scale range
## (`stat_boxplot()`).
library(dplyr)
library(tidyr)
library(ggplot2)
library(scales)
# Hàm CAGR
cagr <- function(x) {
n <- length(x)
(last(x)/first(x))^(1/(n-1)) - 1
}
# Tính các chỉ số
summary_table <- dl %>%
summarise(
tts_cagr = cagr(tts),
vcsh_cagr = cagr(vcsh),
lnsk_cagr = cagr(loinhuanketoansauthuetndn),
tts_sd = sd(tts, na.rm = TRUE),
vcsh_sd = sd(vcsh, na.rm = TRUE),
lnsk_sd = sd(loinhuanketoansauthuetndn, na.rm = TRUE),
roe_range = max(ROE, na.rm = TRUE) - min(ROE, na.rm = TRUE),
roa_range = max(ROA, na.rm = TRUE) - min(ROA, na.rm = TRUE)
)
# Chuyển sang dạng long để vẽ
summary_long <- summary_table %>%
pivot_longer(cols = everything(), names_to = "Metric", values_to = "Value") %>%
mutate(
Type = case_when(
grepl("_cagr$", Metric) ~ "CAGR",
grepl("_sd$|_range$", Metric) ~ "Biến động",
TRUE ~ "Khác"
),
Metric_clean = gsub("_cagr$|_sd$|_range$", "", Metric)
)
# Biểu đồ CAGR và biến động
ggplot(summary_long, aes(x = Metric_clean, y = Value, fill = Type)) +
geom_col(position = "dodge") +
geom_text(aes(label = ifelse(Type == "CAGR", percent(Value, accuracy = 0.1),
comma(round(Value, 0)))),
position = position_dodge(width = 0.9), vjust = -0.3, size = 3.5) +
scale_y_continuous(labels = label_number(scale = 1e-9, suffix = "T")) + # chia tỷ nếu cần
labs(title = "Tăng trưởng và biến động các chỉ tiêu tài chính",
x = "Chỉ tiêu",
y = "Giá trị / Tỷ lệ") +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "top",
plot.title = element_text(face = "bold", size = 14)
)library(reshape2)
library(ggplot2)
library(scales)
vars_fin <- c(
"tts",
"vcsh",
"loinhuanketoansauthuetndn",
"congdoanhthuhoatdong",
"congchiphihoatdong",
"tongloinhuanketoantruocthue",
"chiphithuetndn",
"laicobantrencophieu",
"luuchuyentienthuantuhoatdongkinhdoanh",
"tienvacackhoantuongduongtiendaunam",
"tienvacackhoantuongduongtiencuoinam",
"ROA",
"ROE"
)
cor_matrix <- dl %>%
select(all_of(vars_fin)) %>%
cor(use = "complete.obs")
cor_long <- melt(cor_matrix, varnames = c("X", "Y"), value.name = "Correlation")
ggplot(cor_long, aes(x = X, y = Y, fill = Correlation)) +
geom_tile(color = "white") +
scale_fill_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0,
limit = c(-1, 1), space = "Lab", name = "Tương quan") +
geom_text(aes(label = round(Correlation, 2)), color = "black", size = 3) +
theme_minimal() +
theme(
axis.text.x = element_text(angle = 45, vjust = 1, hjust = 1),
axis.title = element_blank(),
panel.grid = element_blank(),
plot.title = element_text(face = "bold", size = 14)
) +
labs(title = "Heatmap tương quan giữa các chỉ tiêu tài chính")library(ggplot2)
library(dplyr)
library(tidyr)
library(scales)
# Chuẩn bị dữ liệu cột
dl_cash_long <- dl %>%
select(year, tienvacackhoantuongduongtiendaunam, tienvacackhoantuongduongtiencuoinam,
luuchuyentienthuantuhoatdongkinhdoanh) %>%
pivot_longer(cols = -year, names_to = "Type", values_to = "Value")
# Tạo dữ liệu cash_ratio riêng cho geom_line
dl_cash_ratio <- dl %>%
mutate(cash_ratio = tienvacackhoantuongduongtiencuoinam / tts)
# Vẽ biểu đồ
ggplot() +
geom_col(data = dl_cash_long, aes(x = factor(year), y = Value, fill = Type), position = "dodge") +
geom_line(data = dl_cash_ratio, aes(x = factor(year), y = cash_ratio * max(dl$tienvacackhoantuongduongtiencuoinam, na.rm = TRUE), group = 1),
color = "red", size = 1.2) +
scale_y_continuous(
name = "Số tiền (VND)",
labels = comma,
sec.axis = sec_axis(~./max(dl$tienvacackhoantuongduongtiencuoinam, na.rm = TRUE),
name = "Tỷ lệ tiền/TTS")
) +
labs(title = "Phân tích dòng tiền và khả năng thanh toán",
x = "Năm") +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
legend.title = element_blank())## Warning: Using `size` aesthetic for lines was deprecated in ggplot2 3.4.0.
## ℹ Please use `linewidth` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
df_long <- dl %>%
select(year, tts, vcsh, loinhuanketoansauthuetndn, ROA, ROE) %>%
pivot_longer(cols = -year, names_to = "Metric", values_to = "Value")
ggplot(df_long, aes(x = factor(year), y = Value, color = Metric, group = Metric)) +
geom_line(size = 1.2) +
geom_point(size = 2) +
facet_wrap(~Metric, scales = "free_y", ncol = 2) +
scale_y_continuous(labels = comma) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1),
legend.position = "none")dl %>%
mutate(debt_equity = (tts - vcsh) / vcsh) %>%
ggplot(aes(x = factor(year), y = debt_equity)) +
geom_line(size = 1.2, color = "steelblue") +
geom_point(size = 2, color = "steelblue") +
scale_y_continuous(labels = scales::percent_format(accuracy = 1)) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))## `geom_line()`: Each group consists of only one observation.
## ℹ Do you need to adjust the group aesthetic?
dl %>%
select(year, tongloinhuanketoantruocthue, loinhuanketoansauthuetndn) %>%
pivot_longer(cols = -year, names_to = "Metric", values_to = "Value") %>%
ggplot(aes(x = factor(year), y = Value, color = Metric, group = Metric)) +
geom_line(size = 1.2) +
geom_point(size = 2) +
scale_y_continuous(labels = scales::dollar_format()) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))finance_vars <- dl %>%
select(year, tts, vcsh, loinhuanketoansauthuetndn, congdoanhthuhoatdong, congchiphihoatdong)
finance_vars_long <- finance_vars %>%
pivot_longer(cols = -year, names_to = "Metric", values_to = "Value")
ggplot(finance_vars_long, aes(x = factor(year), y = Value, color = Metric, group = Metric)) +
geom_line(size = 1.2) +
geom_point(size = 2) +
scale_y_continuous(labels = scales::dollar_format()) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))dl_long <- dl %>%
select(year, ROA, ROE, loinhuanketoansauthuetndn) %>%
pivot_longer(cols = -year, names_to = "Metric", values_to = "Value")
ggplot(dl_long, aes(x = factor(year), y = Value, color = Metric, group = Metric)) +
geom_line(size = 1.2) +
geom_point(size = 2) +
scale_y_continuous(labels = function(x) ifelse(x < 1, scales::percent(x, accuracy = 0.1), scales::dollar(x))) +
theme_minimal() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))dl_corr <- dl %>%
select(loinhuanketoansauthuetndn, luuchuyentienthuantuhoatdongkinhdoanh, vcsh)
cor_matrix <- round(cor(dl_corr, use = "complete.obs"), 2)
cor_df <- as.data.frame(as.table(cor_matrix))
ggplot(cor_df, aes(Var1, Var2, fill = Freq)) +
geom_tile() +
geom_text(aes(label = Freq), color = "white", size = 5) +
scale_fill_gradient2(low = "blue", mid = "white", high = "red", midpoint = 0) +
theme_minimal() +
labs(x = "", y = "", fill = "Correlation") +
theme(axis.text.x = element_text(angle = 45, hjust = 1))top_cv_plot <- top_cv %>%
mutate(Chi_tieu = factor(Chi_tieu, levels = rev(Chi_tieu)))
ggplot(top_cv_plot, aes(x = Chi_tieu, y = CV)) +
geom_bar(stat = "identity", fill = "steelblue") +
scale_y_continuous(labels = percent_format(accuracy = 1)) +
coord_flip() +
theme_minimal()