CHƯƠNG 1: PHÂN TÍCH BỘ DỮ LIỆU SPOTIFY

1.1 Giới thiệu và châunr bị dữ liệu

1.1.1. Tổng quan về bộ dữ liệu và mục tiêu phân tích

1.1.1.1. Giới thiệu chung và mục tiêu phân tích

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, valencepopularity. - 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.


1.1.1.2. Cấu trúc và đặc điểm của bộ dữ liệu

  • Tên bộ dữ liệu: Spotify Dataset 1921–2020, 160k+ Tracks
  • Nguồn dữ liệu: Kaggle (người đóng góp: Yama Erenay)
  • Phạm vi thời gian: 1921 – 2020
  • Quy mô: Gồm 170.654 quan sát (mỗi quan sát tương ứng với một bài hát)
  • Số lượng biến: 19 biến, bao gồm cả định tính và định lượng
  • Loại dữ liệu: Hỗn hợp – bao gồm cả dữ liệu định tính (tên nghệ sĩ, thể loại, chế độ trưởng/thứ) và định lượng (tempo, loudness, duration, popularity, energy, danceability, v.v.)

1.1.1.3. Mô tả chi tiết các biến trong bộ 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.

1.1.2. Kiểm tra kích thước và chất lượng dữ liệu ban đầu

1.1.2.1. Đọc dữ liệu và kiểm tra kích thước

# 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
cat("Số lượng biến:", num_vars_data, "\n")
## Số lượng biến: 19

Dữ liệu gốc bao gồm 19 biến với 170.653 quan sát.

1.1.2.2. Kiểm tra kiểu dữ liệu và các dòng đầu

# 1. Kiểm tra cấu trúc chi tiết bằng str()
cat("\nCấu trúc dữ liệu (str):\n")
## 
## Cấu trúc dữ liệu (str):
str(data)
## 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")=<externalptr>
# 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"
)
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)
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”.
    Trong đó: Minor biểu thị chế độ thứ (âm hưởng buồn, sâu lắng), còn Major biểu thị chế độ trưởng (âm hưởng vui tươi, sáng sủa).
  • 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")=<externalptr>

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ả.

1.1.2.3. Tóm tắt tổng quan

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ự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 yearrelease_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)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.

1.1.2.4. Tái kiểm tra giá trị thiếu

cat("\nKiểm tra giá trị khuyết thiếu (NA):\n")
## 
## 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.

1.1.2.5. Kiểm tra bản ghi trùng lặp

cat("\nKiểm tra bản ghi trùng lặp:\n")
## 
## 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.

1.1.3. Xử lý dữ liệu, làm sạch và tạo biến phát sinh

1.1.3.1. Chuẩn hoá tên biến

cat("\nChuẩn hóa tên biến trong bộ dữ liệu:\n")
## 
## Chuẩn hóa tên biến trong bộ dữ liệu:
library(janitor)
## 
## 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.1.3.1. Xử lý thời gian và tạo biến chu kỳ

#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:
print(table(data$Era))
## 
##     Cổ điển (Classic Era)     Hiện đại (Modern Era) Kỹ thuật số (Digital Era) 
##                     89452                     39751                     41450
cat("\nKiểm tra phân bố số lượng bài hát theo thập kỷ phát hành:\n")
## 
## Kiểm tra phân bố số lượng bài hát theo thập kỷ phát hành:
print(table(data$Decade))
## 
## 1920s 1930s 1940s 1950s 1960s 1970s 1980s 1990s 2000s 2010s 2020s 
##  5126  9549 15378 19850 19549 20000 19850 19901 19646 19774  2030

1.1.3.3. Tạo biến phát sinh

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")
Bảng 1.1.11: Mô tả các biến mới được tạo trong bộ dữ liệu Spotify
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")
Bảng 1.1.11: Các biến mới có ý nghĩa thực tiễn trong bộ dữ liệu Spotify
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.1.3.4 Phân chia và lưu trữ dữ liệu theo nhóm biến

# 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):
cat("Số biến:", ncol(spotify_group1), "\n")
## Số biến: 16
print(colnames(spotify_group1))
##  [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"
cat("\nNhóm 2 (Độ phổ biến & Thông tin tổng quan):\n")
## 
## Nhóm 2 (Độ phổ biến & Thông tin tổng quan):
cat("Số biến:", ncol(spotify_group2), "\n")
## Số biến: 11
print(colnames(spotify_group2))
##  [1] "name"               "artists"            "release_year"      
##  [4] "release_decade"     "duration_min"       "explicit"          
##  [7] "popularity"         "popularity_level"   "is_explicit_factor"
## [10] "tempo_category"     "release_date"
# 6 Lưu cả ba bộ dữ liệu
save(data, spotify_group1, spotify_group2,
     file = "spotify_groups.RData")

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.3 Phân tích đặc trưng kỹ thuật âm nhạc theo thời gian

1.3.1. Phân tích thống kê mô tả và phân phối các đặc trưng kỹ thuật âm nhạc

1.3.1.1. Thống kê mô tả các đặc trưng kỹ thuật âm nhạc

# 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")
Bảng 1.3.1: Thống kê Mô tả Tổng quan (Đặc trưng kỹ thuật âm nhạc)
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

Nhận xét đặc trưng kỹ thuật âm nhạc

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.

1.3.1.2. Độ lệch chuẩn

# 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")
Bảng 1.3.2: Độ lệch chuẩn (Nhóm biến Đặc trưng kỹ thuật âm nhạc)
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

Nhận xét Bảng 1.3.2: Độ lệch chuẩn (Nhóm biến Đặc trưng kỹ thuật âm nhạc)

  • Độ 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

Kết luậ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

1.3.1.3. Bảng tần suất biến phân loại

library(dplyr)
library(knitr)
library(rlang)
## 
## 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)")
}
## 
## 
## Table: 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)")
}
## 
## 
## Table: 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)")
}
## 
## 
## Table: 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%     |

Nhận xét

  • 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.