Cài đặt các gói

library(moments)
library(psych)                
## Warning: package 'psych' was built under R version 4.5.1
library(readxl)
## Warning: package 'readxl' was built under R version 4.5.1
library(dplyr)
## Warning: package 'dplyr' was built under R version 4.5.1
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(tidyr)
## Warning: package 'tidyr' was built under R version 4.5.1
library(stringr)
## Warning: package 'stringr' was built under R version 4.5.1
library(ggplot2)
## Warning: package 'ggplot2' was built under R version 4.5.1
## 
## Attaching package: 'ggplot2'
## The following objects are masked from 'package:psych':
## 
##     %+%, alpha
library(scales)
## Warning: package 'scales' was built under R version 4.5.1
## 
## Attaching package: 'scales'
## The following objects are masked from 'package:psych':
## 
##     alpha, rescale
library(kableExtra)
## Warning: package 'kableExtra' was built under R version 4.5.1
## 
## Attaching package: 'kableExtra'
## The following object is masked from 'package:dplyr':
## 
##     group_rows
library(janitor)
## Warning: package 'janitor' was built under R version 4.5.1
## 
## Attaching package: 'janitor'
## The following objects are masked from 'package:stats':
## 
##     chisq.test, fisher.test
library(reshape2)
## Warning: package 'reshape2' was built under R version 4.5.1
## 
## Attaching package: 'reshape2'
## The following object is masked from 'package:tidyr':
## 
##     smiths

Chương 1. Phân tích và Trực quan hóa Dữ liệu Người dùng Anime

1. Giới thiệu bộ dữ liệu

1.1 Đọc dữ liệu từ excel

dataset <- read_excel(file.choose())

1.2 Kiểm tra các dòng đầu của dữ liệu

head(dataset)
## # A tibble: 6 × 13
##   `Mal ID` Username Gender `Days Watched` `Mean Score` Watching Completed
##      <dbl> <chr>    <chr>           <dbl>        <dbl>    <dbl>     <dbl>
## 1        1 Xinil    Male            142.          7.37        1       233
## 2        3 Aokaado  Male             68.6         7.34       23       137
## 3        4 Crystal  Female          213.          6.68       16       636
## 4       20 vondur   Male             73.1         8.06       11        94
## 5       36 Baman    Male            272.          5.9        27      1144
## 6       44 beddan   Male             18.6         7.6         0        37
## # ℹ 6 more variables: `On Hold` <dbl>, Dropped <dbl>, `Plan to Watch` <dbl>,
## #   `Total Entries` <dbl>, Rewatched <dbl>, `Episodes Watched` <dbl>

Giải thích: Hàm head() hiển thị 6 dòng đầu tiên của bộ dữ liệu.
Nhận xét: Giúp người phân tích có cái nhìn trực quan về cấu trúc dữ liệu: tên biến, kiểu dữ liệu, dạng giá trị.Phát hiện nhanh lỗi nhập liệu, giá trị NA, hoặc định dạng sai (chẳng hạn cột số nhưng bị lưu dạng ký tự).

1.3 Kiểm tra kích thước dữ liệu

dim(dataset)
## [1] 224383     13

Giải thích: Hàm dim() trả về kích thước của bộ dữ liệu, bao gồm số dòng (quan sát) và số cột (biến).
Nhận xét: Kết quả cho biết bộ dữ liệu có 224,383 quan sát và 13 biến (có 224,383 người dùng và 13 đặc trưng mô tả mỗi người dùng đó).

1.4 Tên các biến

names(dataset)
##  [1] "Mal ID"           "Username"         "Gender"           "Days Watched"    
##  [5] "Mean Score"       "Watching"         "Completed"        "On Hold"         
##  [9] "Dropped"          "Plan to Watch"    "Total Entries"    "Rewatched"       
## [13] "Episodes Watched"

Giải thích: Hàm name() hiển thị tên của tất cả các biến (cột) trong bộ dữ liệu.
Nhận xét: Việc liệt kê tên biến giúp người phân tích nắm được cấu trúc thông tin, phục vụ cho việc chọn lọc, xử lý và trực quan hóa dữ liệu ở các bước sau.

1.5 Kiểm tra và lưu cấu trúc dữ liệu

capture.output(str(dataset))
##  [1] "tibble [224,383 × 13] (S3: tbl_df/tbl/data.frame)"                                      
##  [2] " $ Mal ID          : num [1:224383] 1 3 4 20 36 44 47 66 70 77 ..."                     
##  [3] " $ Username        : chr [1:224383] \"Xinil\" \"Aokaado\" \"Crystal\" \"vondur\" ..."   
##  [4] " $ Gender          : chr [1:224383] \"Male\" \"Male\" \"Female\" \"Male\" ..."          
##  [5] " $ Days Watched    : num [1:224383] 142.3 68.6 212.8 73.1 272.1 ..."                    
##  [6] " $ Mean Score      : num [1:224383] 7.37 7.34 6.68 8.06 5.9 7.6 6.84 7.53 7.18 6.38 ..."
##  [7] " $ Watching        : num [1:224383] 1 23 16 11 27 0 15 34 30 13 ..."                    
##  [8] " $ Completed       : num [1:224383] 233 137 636 94 1144 ..."                            
##  [9] " $ On Hold         : num [1:224383] 8 99 303 11 11 0 22 13 9 0 ..."                     
## [10] " $ Dropped         : num [1:224383] 93 44 0 2 55 0 3 6 8 0 ..."                         
## [11] " $ Plan to Watch   : num [1:224383] 64 40 45 20 338 0 19 10 22 2 ..."                   
## [12] " $ Total Entries   : num [1:224383] 399 343 1000 138 1575 ..."                          
## [13] " $ Rewatched       : num [1:224383] 60 15 10 7 36 0 1 50 15 0 ..."                      
## [14] " $ Episodes Watched: num [1:224383] 8458 4072 12781 4374 16309 ..."

Giải thích:
- Hàm stc() mô tả cấu trúc chi tiết của dataset, bao gồm loại dữ liệu (numeric, character, factor,…) và vài giá trị mẫu của từng biến.
- Hàm capture.output() được dùng để lưu kết quả xuất ra thay vì in ra màn hình.
Nhận xét: Giúp người phân tích hiểu rõ kiểu dữ liệu của từng biến, từ đó chọn phương pháp xử lý phù hợp.

1.6 Tóm tắt nhanh

summary(dataset)
##      Mal ID          Username            Gender           Days Watched      
##  Min.   :      1   Length:224383      Length:224383      Min.   :     0.00  
##  1st Qu.: 131461   Class :character   Class :character   1st Qu.:     6.30  
##  Median : 317908   Mode  :character   Mode  :character   Median :    29.20  
##  Mean   : 384653                                         Mean   :    53.99  
##  3rd Qu.: 481725                                         3rd Qu.:    72.60  
##  Max.   :1291097                                         Max.   :105338.60  
##                                                          NA's   :8          
##    Mean Score        Watching         Completed          On Hold        
##  Min.   : 0.000   Min.   :   0.00   Min.   :    0.0   Min.   :   0.000  
##  1st Qu.: 7.000   1st Qu.:   1.00   1st Qu.:    9.0   1st Qu.:   0.000  
##  Median : 7.800   Median :   4.00   Median :   59.0   Median :   1.000  
##  Mean   : 6.808   Mean   :  10.18   Mean   :  151.9   Mean   :   7.979  
##  3rd Qu.: 8.470   3rd Qu.:  10.00   3rd Qu.:  183.0   3rd Qu.:   7.000  
##  Max.   :10.000   Max.   :2934.00   Max.   :13226.0   Max.   :5167.000  
##  NA's   :8        NA's   :8         NA's   :8         NA's   :8         
##     Dropped         Plan to Watch      Total Entries       Rewatched       
##  Min.   :    0.00   Min.   :    0.00   Min.   :    0.0   Min.   :    0.00  
##  1st Qu.:    0.00   1st Qu.:    0.00   1st Qu.:   16.0   1st Qu.:    0.00  
##  Median :    1.00   Median :    7.00   Median :   94.0   Median :    0.00  
##  Mean   :   10.39   Mean   :   42.11   Mean   :  222.5   Mean   :   10.45  
##  3rd Qu.:    8.00   3rd Qu.:   37.00   3rd Qu.:  279.0   3rd Qu.:    5.00  
##  Max.   :14341.00   Max.   :21804.00   Max.   :24817.0   Max.   :13215.00  
##  NA's   :8          NA's   :8          NA's   :8         NA's   :8         
##  Episodes Watched 
##  Min.   :      0  
##  1st Qu.:    377  
##  Median :   1748  
##  Mean   :   3441  
##  3rd Qu.:   4386  
##  Max.   :5433345  
##  NA's   :8

Giải thích: Hàm summary() cung cấp thống kê mô tả cơ bản cho từng biến.
- Biến định lượng hiển thị min, max, median, mean, quartiles.
- Biến định tính hiển thị tần suất từng mức giá trị.
Nhận xét: Giúp nắm được xu hướng trung tâm, phạm vi giá trị, và phát hiện các giá trị bất thường (outlier) trong dữ liệu.

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

sum(duplicated(dataset))
## [1] 0

Giải thích:
- Hàm duplicated() xác định những dòng (quan sát) bị trùng trong bộ dữ liệu, trả về giá trị logic TRUE/FALSE.
- Hàm sum() cộng tổng số TRUE → tức là đếm số dòng bị trùng lặp. Nếu kết quả > 0, bộ dữ liệu có các bản ghi lặp lại và cần xử lý (loại bỏ hoặc gộp lại). Nếu = 0, dữ liệu là duy nhất, không bị trùng.

1.8 Đếm số biến định lượng

sum(sapply(dataset, is.numeric))
## [1] 11

Giải thích:
- Hàm sapply() áp dụng is.numeric cho từng biến trong dataset để kiểm tra xem biến đó có phải dạng số hay không.
- Hàm sum() sau đó cộng tổng số biến có giá trị TRUE chính là số lượng biến định lượng trong dataset.
Nhận xét: Giúp xác định có bao nhiêu biến có thể sử dụng cho các phép tính thống kê, mô hình hóa hoặc trực quan hóa định lượng.

1.9 Đếm số biến định tính

sum(sapply(dataset, function(x) is.factor(x) | is.character(x)))
## [1] 2

Giải thích:
- Kiểm tra xem từng biến có phải kiểu phân loại (factor) hoặc kiểu chuỗi ký tự (character) hay không.
- Hàm sum() sẽ cộng tổng các biến có kiểu đó trả về số lượng biến định tính.
Nhận xét: Giúp xác định có bao nhiêu biến định tính để dùng trong việc phân tích nhóm, vẽ biểu đồ tần suất, hoặc dùng làm biến phân loại trong mô hình thống kê.

1.10 Tên các biến định lượng

names(dataset)[sapply(dataset, is.numeric)]
##  [1] "Mal ID"           "Days Watched"     "Mean Score"       "Watching"        
##  [5] "Completed"        "On Hold"          "Dropped"          "Plan to Watch"   
##  [9] "Total Entries"    "Rewatched"        "Episodes Watched"

Giải thích: Hàm sapply(dataset, is.numeric) tạo ra một vector logic, TRUE tại những biến có kiểu số. Sau đó names(dataset)[…] lọc ra tên của các biến định lượng.
Nhận xét: Giúp người phân tích biết rõ biến nào có thể sử dụng cho các phép tính trung bình, phương sai, hoặc vẽ biểu đồ dạng histogram, scatter plot,…

1.11 Tên các biến định tính

names(dataset)[sapply(dataset, function(x) is.factor(x) | is.character(x))]
## [1] "Username" "Gender"

Giải thích: Hàm sapply(dataset, function(x) is.factor(x) | is.character(x)) kiểm tra từng cột trong dataset. Trong đó:
- is.factor(x) kiểm tra xem biến có kiểu phân loại (như giới tính, trạng thái xem phim, thể loại, v.v.) không.
- is.character(x) kiểm tra xem biến có kiểu chuỗi ký tự không (ví dụ: tên anime, username, thể loại…).
- Dấu | nghĩa là “hoặc”: chỉ cần thỏa một trong hai điều kiện là TRUE.
- names(dataset)[…] để lấy tên của các biến có giá trị TRUE (biến định tính)
Kết quả trả về danh sách các biến định tính trong bộ dữ liệu, ví dụ như gender, type, status, title,…
Những biến này được dùng cho việc phân tích nhóm hoặc tần suất (đếm số lượng từng loại), vẽ biểu đồ cột, biểu đồ tròn hay phân tích mối quan hệ giữa các nhóm.
Nhận xét: Việc xác định các biến định tính giúp người phân tích biết được đâu là biến phân loại, qua đó chọn kỹ thuật xử lý và trực quan hóa phù hợp.

1.12 Giải thích ý nghĩa các biến

variable_meaning <- data.frame(
  Variable = c("mal_id", "gender", "type", "episodes_watched", "mean_score", 
               "days_watched", "rewatched", "on_hold", "dropped", "completed", 
               "plan_to_watch", "total_entries", "title"),
  Meaning = c("Mã định danh duy nhất của anime trên hệ thống (MyAnimeList ID)",
              "Giới tính của người dùng (Nam/Nữ/Khác)",
              "Loại anime (TV, Movie, OVA, ONA, Special, ...)",
              "Số tập phim mà người dùng đã xem",
              "Điểm trung bình mà người dùng đánh giá anime (thang điểm 1–10)",
              "Tổng số ngày mà người dùng dành để xem anime",
              "Số lần người dùng xem lại anime (Rewatched)",
              "Số anime người dùng đang tạm dừng xem (On-hold)",
              "Số anime người dùng đã bỏ xem (Dropped)",
              "Số anime người dùng đã hoàn thành (Completed)",
              "Số anime người dùng dự định sẽ xem (Plan to watch)",
              "Tổng số anime mà người dùng có trong danh sách xem",
              "Tên anime hoặc series"))
variable_meaning
##            Variable
## 1            mal_id
## 2            gender
## 3              type
## 4  episodes_watched
## 5        mean_score
## 6      days_watched
## 7         rewatched
## 8           on_hold
## 9           dropped
## 10        completed
## 11    plan_to_watch
## 12    total_entries
## 13            title
##                                                           Meaning
## 1  Mã định danh duy nhất của anime trên hệ thống (MyAnimeList ID)
## 2                          Giới tính của người dùng (Nam/Nữ/Khác)
## 3                  Loại anime (TV, Movie, OVA, ONA, Special, ...)
## 4                                Số tập phim mà người dùng đã xem
## 5  Điểm trung bình mà người dùng đánh giá anime (thang điểm 1–10)
## 6                    Tổng số ngày mà người dùng dành để xem anime
## 7                     Số lần người dùng xem lại anime (Rewatched)
## 8                 Số anime người dùng đang tạm dừng xem (On-hold)
## 9                         Số anime người dùng đã bỏ xem (Dropped)
## 10                  Số anime người dùng đã hoàn thành (Completed)
## 11             Số anime người dùng dự định sẽ xem (Plan to watch)
## 12             Tổng số anime mà người dùng có trong danh sách xem
## 13                                          Tên anime hoặc series

Giải thích:
- Dùng hàm data.frame() để tạo một bảng dữ liệu mới (data frame). Mỗi cột trong bảng được truyền vào qua đối số, có thể là vector, list hoặc giá trị cụ thể.
- Dùng Variable = c(…) để tạo một vector chứa tên các biến có trong bộ dữ liệu gốc (dataset). Các tên biến như mal_id, gender, type, episodes_watched,… được liệt kê đầy đủ để đảm bảo người đọc biết rõ từng biến xuất hiện trong dataset. Đây là cột đầu tiên trong bảng variable_meaning, giữ vai trò liệt kê biến gốc.
- Dùng Meaning = c(…), cột thứ hai là một vector mô tả ý nghĩa cụ thể của từng biến. Mỗi phần tử tương ứng 1 dòng với biến ở cột Variable.
- Variable_meaning <- … Dấu <- dùng để gán kết quả (tức là bảng mới vừa tạo) vào một biến có tên variable_meaning. Sau khi chạy lệnh này, người thực hiện có thể xem bảng bằng cách gọi variable_meaning ở dòng sau.
- variable_meaning: Khi gọi riêng tên bảng, R sẽ hiển thị nội dung của nó ra màn hình. Kết quả là một bảng gồm 2 cột là Variable (Tên biến trong dữ liệu gốc) và Meaning (Giải thích ý nghĩa của biến đó).
Nhận xét: Giúp người phân tích nắm bắt nhanh và rõ ràng nội dung của từng biến, đảm bảo quá trình phân tích, trực quan hóa và diễn giải kết quả được chính xác, logic và có cơ sở.

2. Xử lý dữ liệu thô, mã hóa dữ liệu

2.1 Chuẩn hóa tên cột (viết thường, thay khoảng trắng và ký tự lạ bằng “_“)

colnames(dataset) <- colnames(dataset) %>%
  str_to_lower() %>%
  str_replace_all("\\s+", "_") %>%       
  str_replace_all("[^a-z0-9_]", "")  

Giải thích: Lấy tên cột hiện tại (colnames(dataset)), chuyển tất cả thành chữ thường (str_to_lower()), thay mọi khoảng trắng (1 hoặc nhiều) bằng dấu gạch dưới _ (str_replace_all(“\s+”, “”)), rồi loại bỏ mọi ký tự không phải chữ cái thường, chữ số hoặc (str_replace_all(”[^a-z0-9_]“,”“)).
Nhận xét: Việc chuẩn hóa tên cột giúp bộ dữ liệu đạt được tính thống nhất và chuyên nghiệp trong quá trình xử lý. Các tên biến sau khi chuyển thành chữ thường, loại bỏ khoảng trắng và ký tự đặc biệt sẽ giúp hạn chế tối đa lỗi cú pháp khi gọi biến trong R, đồng thời đảm bảo khả năng tương thích cao với các gói phân tích (đặc biệt là tidyverse). Bên cạnh đó, thao tác này còn giúp việc đọc hiểu tên biến trở nên trực quan, dễ thao tác và đồng nhất.

2.2 Chuyển những cột numeric lưu dưới dạng text (có dấu phẩy) sang numeric

num_candidates <- c("score","members","episodes","popularity","votes")
for (v in num_candidates) {
  if (v %in% colnames(dataset)) {
    dataset[[v]] <- as.numeric(str_replace_all(as.character(dataset[[v]]), ",", ""))
  }
}

Giải thích: Với từng tên trong num_candidates nếu có trong dataset, mã sẽ:
- Ép kiểu sang character (để chắc chắn thao tác string)
- Loại bỏ dấu phẩy , (ví dụ “1,234” → “1234”) bằng str_replace_all,
- Chuyển sang numeric bằng as.numeric.
Nhận xét: Dữ liệu định lượng nếu được lưu ở dạng ký tự (text) sẽ không thể thực hiện các phép tính thống kê hay mô hình hoá. Do đó, việc chuyển đổi về dạng numeric là bước quan trọng nhằm chuẩn bị dữ liệu cho quá trình phân tích sau này. Đồng thời, việc loại bỏ dấu phẩy trong các giá trị như “1,000” giúp tránh lỗi ép kiểu và đảm bảo tính chính xác của các phép tính trung bình, phương sai hoặc hồi quy.

2.3 Loại bỏ khoảng trắng thừa và chuẩn hóa chữ cho cột text

dataset <- dataset %>%
  mutate(
    username = str_trim(username),
    gender = str_trim(gender)
  )

Giải thích: Hàm str_trim() loại bỏ khoảng trắng ở đầu và cuối chuỗi (leading/trailing). mutate() cập nhật lại hai cột username và gender.
Nhận xét: Khoảng trắng dư thừa hoặc định dạng không nhất quán trong chuỗi ký tự có thể khiến các thao tác nhóm hoặc lọc dữ liệu bị sai lệch. Việc loại bỏ khoảng trắng và chuẩn hóa chữ giúp các biến dạng text (như username, gender) được đồng nhất, từ đó đảm bảo độ tin cậy cho các phân tích so sánh hoặc thống kê tần suất. Đây là một bước nhỏ nhưng đóng vai trò quan trọng trong việc làm sạch dữ liệu.

2.4 Mã hóa biến phân loại (categorical) thành factor (ví dụ type, rating)

for (cname in c("type","rating","status","popularity_bucket")) {
  if (cname %in% colnames(dataset)) dataset[[cname]] <- as.factor(dataset[[cname]])
}

Giải thích: Với mỗi tên cột liệt kê, nếu tồn tại trong dataset thì chuyển kiểu của cột đó thành factor.
Nhận xét: Biến factor trong R được hiểu như biến định tính có giới hạn số lượng giá trị (levels). Việc chuyển các cột như type, rating, status, popularity_bucket sang dạng factor giúp chương trình nhận biết được đây là biến phân loại, không phải biến số. Điều này giúp cho quá trình trực quan hóa trở nên chính xác và có ý nghĩa thống kê hơn.

2.5 Chuẩn hóa giới tính (viết hoa chữ cái đầu)

dataset <- dataset %>%
  mutate(
    gender = case_when(
      str_to_lower(gender) %in% c("male", "m") ~ "Male",
      str_to_lower(gender) %in% c("female", "f") ~ "Female",
      TRUE ~ gender
    )
  )

Giải thích: Dùng case_when() để chuẩn hóa giá trị trong cột gender: các biến thể như “male”, “M” → “Male”, tương tự cho “Female”. Các giá trị khác giữ nguyên.
Nhận xét: Giới tính là biến thường xuyên bị nhập dữ liệu không đồng nhất (ví dụ “Male”, “male”, “M”, “m”). Việc chuẩn hóa các giá trị này về cùng một dạng giúp dữ liệu trở nên đồng nhất, dễ đọc và dễ xử lý. Đồng thời, việc thống nhất cách viết (viết hoa chữ cái đầu) còn giúp biểu đồ, bảng thống kê trở nên nhất quán và chuyên nghiệp hơn.

2.6 Tạo cột genre_list nếu có cột genre chứa nhiều thể loại trong 1 ô

if ("genre" %in% colnames(dataset)) {
  dataset <- dataset %>% mutate(genre_list = str_split(genre, ",\\s*"))
}

Giải thích: Nếu tồn tại cột genre (chuỗi chứa nhiều thể loại phân tách bằng dấu phẩy), str_split(…, “,\s*“) sẽ tách mỗi ô thành một list các thể loại và lưu vào cột genre_list.
Nhận xét: Trong nhiều tập dữ liệu, một bản ghi có thể thuộc nhiều thể loại (multi-label). Việc tách cột genre thành danh sách riêng (genre_list) cho phép khai thác sâu hơn các mối quan hệ giữa thể loại, giúp người phân tích dễ dàng thực hiện thống kê theo từng nhãn riêng biệt. Nhờ đó, có thể thực hiện các phân tích đa chiều.

2.7 Impute NA cho numeric bằng median (giữ trung vị tránh ảnh hưởng outliers)

numeric_cols <- names(dataset)[sapply(dataset, is.numeric)]
for (nc in numeric_cols) {
  na_count <- sum(is.na(dataset[[nc]]))
  if (na_count > 0) {
    dataset[[nc]][is.na(dataset[[nc]])] <- median(dataset[[nc]], na.rm = TRUE)
    message("Imputed ", na_count, " NA in ", nc, " with median.")
  }
}
## Imputed 8 NA in days_watched with median.
## Imputed 8 NA in mean_score with median.
## Imputed 8 NA in watching with median.
## Imputed 8 NA in completed with median.
## Imputed 8 NA in on_hold with median.
## Imputed 8 NA in dropped with median.
## Imputed 8 NA in plan_to_watch with median.
## Imputed 8 NA in total_entries with median.
## Imputed 8 NA in rewatched with median.
## Imputed 8 NA in episodes_watched with median.

Giải thích: Xác định các cột numeric,với mỗi cột có NA, thay các giá trị NA bằng median của cột đó và in thông báo. Vì Median là lựa chọn robust (không bị ảnh hưởng mạnh bởi outlier) so với mean. Việc impute giữ kích thước mẫu đầy đủ để thuận tiện cho phân tích hoặc mô hình mà không làm méo phân bố quá nhiều.
Nhận xét: Trung vị (median) là một thước đo đại diện cho xu hướng trung tâm, ít bị ảnh hưởng bởi các giá trị ngoại lai. Việc thay thế các giá trị thiếu (NA) trong biến số bằng trung vị giúp giữ lại kích thước mẫu đầy đủ, đảm bảo dữ liệu không bị mất mát khi thực hiện các mô hình hồi quy hoặc phân tích mô tả. Đồng thời, phương pháp này giúp giảm thiểu sai lệch so với việc loại bỏ hàng chứa giá trị thiếu.

2.8 Thay NA cho factor bằng level ‘unknown’ (giữ thông tin thiếu)

fac_cols <- names(dataset)[sapply(dataset, is.factor)]
for (fc in fac_cols) dataset[[fc]] <- fct_explicit_na(dataset[[fc]], na_level = "unknown")

Giải thích: Tìm các cột kiểu factor và biến các giá trị NA thành một level mới có tên “unknown” bằng forcats::fct_explicit_na(). Việc giữ NA như NA có thể gây lỗi khi vẽ hoặc khi chuyển dữ liệu cho mô hình (một số hàm bỏ NA). Biến NA thành level unknown vừa giữ thông tin rằng “thiếu dữ liệu”, vừa cho phép phân tích tần suất, so sánh và trực quan hóa nhóm unknown.
Nhận xét: Thay vì loại bỏ các giá trị bị thiếu, việc gán nhãn “unknown” giúp bảo toàn thông tin và phản ánh rõ ràng tình trạng “thiếu dữ liệu” trong phân tích. Cách xử lý này có thể hữu ích trong việc mô tả dữ liệu, giúp người đọc dễ dàng nhận thấy tỷ lệ thiếu trong từng nhóm, đồng thời tránh lỗi khi vẽ biểu đồ hoặc khi chuyển dữ liệu vào mô hình học máy.

2.9 Loại bỏ duplicate theo cặp (title, release_year) nếu hai cột tồn tại

if (all(c("title","release_year") %in% colnames(dataset))) {
  dataset <- dataset %>% distinct(title, release_year, .keep_all = TRUE)
}

Giải thích: Nếu cả hai cột tồn tại, distinct(title, release_year, .keep_all = TRUE) giữ lại bản ghi đầu tiên cho mỗi cặp (title, release_year) và loại bỏ các bản ghi trùng lặp.
Nhận xét: Các bản ghi trùng lặp có thể làm sai lệch kết quả thống kê, đặc biệt là khi tính số lượng hoặc trung bình. Việc loại bỏ trùng lặp theo cặp (title, release_year) giúp đảm bảo tính duy nhất cho mỗi bộ phim trong từng năm phát hành, góp phần làm cho tập dữ liệu trở nên chính xác, tin cậy và phản ánh đúng thực tế.

2.10 Tạo biến tầng popularity_bucket theo quantiles (Very High/High/Medium/Low)

if ("members" %in% colnames(dataset)) {
  dataset <- dataset %>% mutate(
    popularity_bucket = case_when(
      members >= quantile(members, .90, na.rm=TRUE) ~ "very_high",
      members >= quantile(members, .75, na.rm=TRUE) ~ "high",
      members >= quantile(members, .50, na.rm=TRUE) ~ "medium",
      TRUE ~ "low"
    )
  )
  dataset$popularity_bucket <- factor(dataset$popularity_bucket, levels = c("low","medium","high","very_high"))
}

Giải thích: Sử dụng phân vị (quantile) của cột members để chia thành 4 nhóm: low (<50th), medium (50–75), high (75–90), very_high (≥90th). Sau đó chuyển sang factor với thứ tự mong muốn.
Nhận xét: Việc phân nhóm mức độ phổ biến dựa trên các phân vị (quantiles) giúp chia dữ liệu thành các nhóm có ý nghĩa thống kê như “low”, “medium”, “high”, “very high”. Nhờ đó, người phân tích có thể dễ dàng nhận biết sự khác biệt giữa các nhóm, phục vụ cho việc so sánh, trực quan hóa và xây dựng mô hình dự đoán. Bước này không chỉ giúp dữ liệu trở nên dễ hiểu mà còn tăng giá trị ứng dụng thực tiễn trong việc đánh giá xu hướng phổ biến của từng bộ phim hoặc tác phẩm.

3. Thống kê mô tả

3.1 Chọn ra các biến định lượng để thống kê

num_data <- dataset %>%
  select(days_watched, mean_score, watching, completed, on_hold,
         dropped, plan_to_watch, total_entries, rewatched, episodes_watched)

Giải thích: Lệnh select() trong gói dplyr được dùng để chọn ra các cột định lượng cần thống kê mô tả. Ở đây, ta chọn 10 biến thể hiện hành vi xem anime của người dùng như: số ngày đã xem (days_watched), điểm trung bình (mean_score), số lượng anime đã hoàn thành (completed), đang xem (watching), tạm hoãn (on_hold), bỏ xem (dropped),dự định xem (plan_to_watch),Tổng số anime mà người dùng có trong danh sách xem (total_entries),xem lại (rewatched), tập phim đã xem (episodes_watched).
Nhận xét: Việc tách riêng dữ liệu định lượng giúp cho quá trình phân tích thống kê trở nên chính xác và thuận tiện hơn, vì các phép tính như trung bình, phương sai hay độ lệch chuẩn chỉ áp dụng cho biến số.

3.2 Thống kê cơ bản

3.2.1 Xem tổng quan dữ liệu định lượng

summary(num_data)
##   days_watched         mean_score        watching         completed      
##  Min.   :     0.00   Min.   : 0.000   Min.   :   0.00   Min.   :    0.0  
##  1st Qu.:     6.30   1st Qu.: 7.000   1st Qu.:   1.00   1st Qu.:    9.0  
##  Median :    29.20   Median : 7.800   Median :   4.00   Median :   59.0  
##  Mean   :    53.99   Mean   : 6.808   Mean   :  10.18   Mean   :  151.9  
##  3rd Qu.:    72.60   3rd Qu.: 8.470   3rd Qu.:  10.00   3rd Qu.:  183.0  
##  Max.   :105338.60   Max.   :10.000   Max.   :2934.00   Max.   :13226.0  
##     on_hold            dropped         plan_to_watch      total_entries    
##  Min.   :   0.000   Min.   :    0.00   Min.   :    0.00   Min.   :    0.0  
##  1st Qu.:   0.000   1st Qu.:    0.00   1st Qu.:    0.00   1st Qu.:   16.0  
##  Median :   1.000   Median :    1.00   Median :    7.00   Median :   94.0  
##  Mean   :   7.979   Mean   :   10.39   Mean   :   42.11   Mean   :  222.5  
##  3rd Qu.:   7.000   3rd Qu.:    8.00   3rd Qu.:   37.00   3rd Qu.:  279.0  
##  Max.   :5167.000   Max.   :14341.00   Max.   :21804.00   Max.   :24817.0  
##    rewatched        episodes_watched 
##  Min.   :    0.00   Min.   :      0  
##  1st Qu.:    0.00   1st Qu.:    377  
##  Median :    0.00   Median :   1748  
##  Mean   :   10.45   Mean   :   3441  
##  3rd Qu.:    5.00   3rd Qu.:   4386  
##  Max.   :13215.00   Max.   :5433345

Giải thích: Hàm summary() cung cấp tóm tắt thống kê cơ bản cho từng biến: giá trị nhỏ nhất (Min), giá trị lớn nhất (Max), trung vị (Median), giá trị trung bình (Mean) và các tứ phân vị (1st Qu., 3rd Qu.).
Nhận xét: Kết quả giúp nắm được đặc điểm phân bố và mức độ biến thiên của dữ liệu. Ví dụ, nếu mean và median chênh lệch nhiều, có thể dữ liệu bị lệch (skewed).

3.2.2 Trung bình(Mean)

mean_days <- mean(num_data$days_watched, na.rm=TRUE)
mean_days
## [1] 53.99096
mean_score <- mean(num_data$mean_score, na.rm=TRUE)
mean_score
## [1] 6.808495
mean_completed <- mean(num_data$completed, na.rm=TRUE)
mean_completed
## [1] 151.863

Giải thích: Hàm mean() tính giá trị trung bình của từng biến, giúp biết mức độ “đại diện” của dữ liệu.
Tham số na.rm=TRUE được thêm vào để loại bỏ các giá trị thiếu (NA) khi tính toán.
- mean_days_watched: cho biết trung bình người dùng đã xem anime trong bao nhiêu ngày.
- mean_mean_score: thể hiện mức điểm trung bình mà người dùng thường chấm.
- mean_completed: trung bình số lượng anime đã hoàn thành của mỗi người dùng.
Nhận xét: Nếu giá trị trung bình của days_watched lớn và mean_score ở mức cao, điều đó cho thấy nhóm người dùng hoạt động tích cực và có mức đánh giá khá cao.
Kết quả cho thấy trung bình mỗi người dùng trong tập dữ liệu đã xem anime trong khoảng 54 ngày. Con số này cho thấy phần lớn người dùng duy trì thói quen xem anime trong thời gian khá dài, thể hiện mức độ gắn bó tương đối cao với nội dung.
Trung bình điểm đánh giá anime là 6.8/10, nằm ở mức khá. Điều này phản ánh rằng đa số người xem có đánh giá tích cực nhưng chưa thật sự xuất sắc có thể do chênh lệch về chất lượng các bộ anime hoặc tiêu chí đánh giá của người xem.
Trung bình mỗi người dùng đã hoàn thành khoảng 152 bộ anime. Đây là con số rất cao, cho thấy nhóm mẫu trong dữ liệu bao gồm nhiều người dùng hoạt động tích cực, có thể là những fan lâu năm hoặc người xem thường xuyên.

3.2.3 Trung vị (Median)

median_days_watched <- median(num_data$days_watched, na.rm=TRUE)
median_days_watched
## [1] 29.2
median_mean_score <- median(num_data$mean_score, na.rm=TRUE)
median_mean_score
## [1] 7.8

Giải thích:
- median(x, na.rm=TRUE) tính trung vị của biến x; na.rm=TRUE loại bỏ giá trị NA trước khi tính.
- Gán kết quả vào median_days_watched, median_mean_score để sử dụng tiếp.
Nhận xét: Trung vị phản ánh giá trị nằm giữa phân bố dữ liệu, tức là 50% quan sát có giá trị nhỏ hơn hoặc bằng trung vị và 50% còn lại lớn hơn hoặc bằng. Đây là chỉ tiêu quan trọng khi dữ liệu có ngoại lệ hoặc phân bố lệch, vì trung vị ít bị ảnh hưởng bởi các giá trị cực đoan hơn trung bình. Trong đó, trung vị của days_watched thể hiện số ngày xem trung bình của người dùng điển hình trong mẫu dữ liệu, còn trung vị của mean_score phản ánh mức điểm đánh giá trung tâm mà người dùng thường dành cho các bộ anime.
Kết quả cho thấy một nửa số người dùng xem anime ít hơn 29 ngày, còn một nửa xem nhiều hơn 29 ngày. So với giá trị trung bình (≈54 ngày), điều này cho thấy phân phối dữ liệu lệch phải (right-skewed) nghĩa là vẫn có một nhóm nhỏ người xem cực kỳ nhiều (outlier), kéo giá trị trung bình lên cao.
Mức trung vị này cao hơn trung bình (6.8) cho thấy đa số người dùng chấm điểm cao hơn 7, trong khi một số ít đánh giá rất thấp đã kéo điểm trung bình xuống. Điều này phản ánh xu hướng đánh giá tích cực chiếm ưu thế trong tập dữ liệu.

3.2.4 Giá trị nhỏ nhất(Min)

min_days_watched <- min(num_data$days_watched, na.rm=TRUE)
min_days_watched
## [1] 0
min_mean_score <- min(num_data$mean_score, na.rm=TRUE)
min_mean_score
## [1] 0

Giải thích: Hàm min() được sử dụng để tìm giá trị nhỏ nhất của từng biến, đồng thời loại bỏ các giá trị NA với tham số na.rm = TRUE.
Nhận xét: Giá trị nhỏ nhất cho thấy giới hạn thấp nhất mà dữ liệu có thể đạt được. Với biến days_watched, giá trị này giúp xác định liệu có người dùng nào chưa xem ngày nào (0 ngày) hay không. Còn đối với mean_score, giá trị nhỏ nhất thể hiện mức đánh giá thấp nhất mà người dùng đã chấm. Việc xác định giá trị nhỏ nhất cũng giúp phát hiện các bất thường trong dữ liệu, chẳng hạn như điểm âm hoặc số ngày xem không hợp lý.

3.2.5 Giá trị lớn nhất

max_days_watched <- max(num_data$days_watched, na.rm=TRUE)
max_days_watched
## [1] 105338.6
max_mean_score <- max(num_data$mean_score, na.rm=TRUE)
max_mean_score
## [1] 10

Giải thích: Hàm max() được dùng để xác định giá trị lớn nhất trong mỗi biến định lượng, loại bỏ các giá trị thiếu.
Nhận xét: Giá trị lớn nhất thể hiện mức độ cực đại trong dữ liệu. Đối với days_watched, nó cho biết người dùng xem nhiều nhất bao nhiêu ngày, giúp nhận diện hành vi “xem nhiều bất thường” (outlier) trong mẫu. Còn với mean_score, giá trị lớn nhất phản ánh điểm số tối đa mà người dùng có thể chấm. Việc biết được giới hạn trên này giúp đánh giá mức độ đa dạng và sự lan tỏa trong hành vi người dùng.

3.2.6 Độ lệch chuẩn (Standard Deviation)

sd_days_watched <- sd(num_data$days_watched, na.rm=TRUE)
sd_days_watched
## [1] 236.2184
sd_mean_score <- sd(num_data$mean_score, na.rm=TRUE)
sd_mean_score
## [1] 2.991183

Giải thích: Sử dụng hàm sd() để tính độ lệch chuẩn là thước đo mức độ phân tán của dữ liệu quanh giá trị trung bình.
Nhận xét: Độ lệch chuẩn cho biết dữ liệu có dao động nhiều hay ít so với trung bình. Nếu độ lệch chuẩn của days_watched lớn, điều đó chứng tỏ sự khác biệt rõ rệt giữa người xem ít và người xem nhiều; còn nếu nhỏ, phần lớn người dùng có thói quen xem tương đối giống nhau. Đối với mean_score, độ lệch chuẩn cao thể hiện sự đa dạng trong quan điểm đánh giá giữa người dùng, còn độ lệch chuẩn thấp chứng tỏ các đánh giá tương đối đồng nhất. Chỉ tiêu này đặc biệt quan trọng khi muốn đánh giá tính ổn định và độ tập trung của dữ liệu. Kết quả cho thấy độ lệch chuẩn bằng 236.2184 rất cao so với giá trị trung bình (≈54 ngày), cho thấy mức độ phân tán lớn trong thời gian xem anime của người dùng. Điều này nghĩa là có sự khác biệt rõ rệt giữa những người xem ít (chỉ vài ngày) và những người xem rất nhiều (hàng trăm ngày). Phân phối này lệch phải mạnh (right-skewed) và thể hiện sự tồn tại của một nhóm nhỏ “người xem cường độ cao” kéo độ lệch chuẩn lên cao.
Đối với mean_score (SD = 2.99): Với thang điểm 1–10, độ lệch chuẩn gần 3 cho thấy mức độ chênh lệch đánh giá khá lớn. Nghĩa là người dùng có quan điểm rất khác nhau khi đánh giá anime có người cho điểm rất thấp (dưới 4), nhưng cũng có nhóm chấm rất cao (9–10). Điều này phản ánh sự đa dạng trong cảm nhận cá nhân, có thể do khác biệt về thể loại, độ tuổi hoặc tiêu chuẩn đánh giá của từng người.

3.2.7 Phương sai (Variance)

var_days_watched <- var(num_data$days_watched, na.rm=TRUE)
var_days_watched
## [1] 55799.13
var_mean_score <- var(num_data$mean_score, na.rm=TRUE)
var_mean_score
## [1] 8.947179

Giải thích: Hàm var() trong R được dùng để tính phương sai của một biến định lượng. Tham số na.rm=TRUE giúp loại bỏ các giá trị bị thiếu (NA) trong quá trình tính toán. Ở đây, ta lần lượt tính phương sai cho hai biến days_watched (số ngày xem trung bình) và mean_score (điểm trung bình đánh giá anime).
Nhận xét: Phương sai của days_watched cho thấy mức độ chênh lệch trong thói quen xem anime giữa các người dùng. Nếu phương sai cao, có nghĩa là có sự khác biệt lớn giữa những người xem ít và người xem nhiều — thể hiện tính đa dạng trong mức độ “gắn bó” với anime.
Ngược lại, phương sai của mean_score thể hiện mức độ khác biệt trong cách người dùng đánh giá anime. Nếu giá trị này thấp, người dùng có xu hướng đồng thuận khi chấm điểm (điểm đánh giá tương đối đồng đều). Nếu cao, điều đó cho thấy ý kiến về chất lượng anime rất phân tán, có thể do khác biệt về gu, thể loại hoặc độ nổi tiếng của từng bộ phim.
Kết quả cho thấy phương sai của day_watched xấp sỉ 55799 con số này rất lớn, phù hợp với độ lệch chuẩn cao, cho thấy dữ liệu có mức độ dao động mạnh. Người dùng trong mẫu nghiên cứu không đồng nhất về thói quen xem anime — một số xem rất ít, trong khi một số khác xem cực kỳ nhiều.
Phương sai của mean_score (≈ 8.95): So với thang điểm 1–10, đây cũng là một phương sai khá đáng kể. Điều này cho thấy sự không nhất quán trong đánh giá chất lượng anime: cùng một bộ phim có thể được chấm rất cao hoặc rất thấp tùy người xem.

3.2.8 Khoảng biến thiên (Range)

range_days_watched <- max_days_watched - min_days_watched
range_days_watched
## [1] 105338.6
range_mean_score <- max_mean_score - min_mean_score
range_mean_score
## [1] 10

Giải thích: Khoảng biến thiên được tính bằng hiệu giữa giá trị lớn nhất (max_days_watched, max_mean_score) và giá trị nhỏ nhất (min_days_watched, min_mean_score). Đây là một cách đơn giản để đo lường độ trải rộng của dữ liệu.
Nhận xét: Khoảng biến thiên của days_watched giúp xác định mức độ chênh lệch cực đại trong thời gian xem anime của người dùng. Nếu khoảng này lớn, nghĩa là có người chỉ xem vài ngày, trong khi người khác xem hàng trăm hoặc hàng nghìn ngày phản ánh sự khác biệt lớn trong cường độ yêu thích anime. Trong khi đó, khoảng biến thiên của mean_score thể hiện biên độ đánh giá của người dùng. Ví dụ, nếu khoảng biến thiên chỉ dao động từ 6 đến 9, điều đó chứng tỏ phần lớn anime được đánh giá khá cao. Nếu trải dài từ 1 đến 10, nghĩa là có nhiều phim bị đánh giá rất thấp và rất cao thể hiện tính phân hóa mạnh về chất lượng hoặc cảm nhận người xem. Kết quả cho thấy giá trị cao nhất của số ngày xem và thấp nhất chênh lệch nhau 105838,6 ngày. Điều này cho thấy dữ liệu có mức độ phân tán rất lớn, có thể tồn tại những người dùng xem anime trong thời gian cực kỳ dài so với những người xem rất ít. Đây là dấu hiệu của phân phối lệch phải (skewed), và có thể có outlier (giá trị ngoại lai). Đối với mean_score có range bằng 10 chứng tỏ dữ liệu đánh giá bao phủ đủ toàn bộ thang điểm, tức có cả người dùng chấm điểm rất thấp và rất cao.

3.2.9 Khoảng tứ phân vị (IQR)

iqr_days_watched <- IQR(num_data$days_watched, na.rm=TRUE)
iqr_days_watched
## [1] 66.3
iqr_mean_score <- IQR(num_data$mean_score, na.rm=TRUE)
iqr_mean_score
## [1] 1.47

Giải thích: Hàm IQR() tính khoảng tứ phân vị, là hiệu giữa phân vị thứ 3 (Q3) và phân vị thứ 1 (Q1). Nó biểu thị phạm vi mà 50% giá trị trung tâm của dữ liệu nằm trong đó.
Nhận xét: Nếu IQR của days_watched nhỏ, điều đó cho thấy phần lớn người dùng có thói quen xem tương đối giống nhau. Nếu lớn, nghĩa là nhóm người dùng trung bình có sự khác biệt đáng kể trong số ngày xem. Tương tự, IQR của mean_score cho biết mức độ đa dạng trong cách chấm điểm ở nhóm người dùng trung bình. Nếu nhỏ, các đánh giá khá đồng nhất; nếu lớn, điều đó thể hiện người dùng đánh giá anime một cách rất khác nhau, có thể do sự khác biệt về thể loại hoặc độ nổi tiếng của từng bộ phim. Kết quả cho thấy:
- Đối với days_watched có IQR bằng 66.3 nghĩa là 50% người dùng nằm trong khoảng giữa (giữa Q1 và Q3) có số ngày xem chênh lệch nhau 66,3 ngày. Mức này tương đối lớn, phản ánh mức độ phân tán cao của hành vi xem anime.
- Đối với mean_score có IQR bằng 1.47 cho thấy phần lớn người dùng đánh giá anime trong khoảng hẹp chỉ khoảng 1,47 điểm quanh trung vị hay hành vi chấm điểm tương đối ổn định, ít biến động, chủ yếu tập trung quanh mức trung bình.

3.2.10 Các tứ phân vị cụ thể

quantile(num_data$days_watched, probs=c(0.25,0.5,0.75), na.rm=TRUE)
##  25%  50%  75% 
##  6.3 29.2 72.6
quantile(num_data$mean_score, probs=c(0.25,0.5,0.75), na.rm=TRUE)
##  25%  50%  75% 
## 7.00 7.80 8.47

Giải thích: quantile() trả về các giá trị phân vị: Q1 (25%), Q2 (50% - trung vị) và Q3 (75%). Mỗi phân vị cho biết mốc mà tại đó một tỷ lệ phần trăm người dùng có giá trị thấp hơn.
Nhận xét:
- Đối với days_watched, Q1 – Q3 cho thấy mức xem anime phổ biến của nhóm người dùng trung bình. Nếu Q1 thấp nhưng Q3 rất cao, nghĩa là chỉ một nhóm nhỏ xem cực kỳ nhiều anime, trong khi đa số xem ít.
- Với mean_score, các phân vị này giúp xác định xu hướng đánh giá. Ví dụ, nếu cả Q1, Q2, Q3 đều cao (>7), điều đó cho thấy phần lớn anime được đánh giá tích cực; ngược lại, nếu trung vị thấp, có thể người dùng đánh giá nghiêm khắc hơn. Kết quả cho thấy:
Đối với biến days_watched 25% người dùng xem ít hơn 6.3 ngày, 50% xem ít hơn 29.2 ngày và 75% xem ít hơn 72.6 ngày. Có thể thấy đa số người dùng xem trong khoảng 30 đến 80 ngày, chỉ một số ít vượt hơn mức này.
Đối với biến mean_score 25% người dùng đánh giá anime thấp hơn 7 điểm, 50% đánh giá dưới 7.8 điểm, và 75% dưới 8.47 điểm.Cho thấy phần lớn người dùng chấm điểm khá cao (trên 7), cho thấy xu hướng tích cực trong đánh giá, chỉ có số ít người cho điểm thấp hơn đáng kể.

3.2.11 Độ lệch (Skewness)

skew_days_watched <- skewness(num_data$days_watched, na.rm=TRUE)
skew_days_watched
## [1] 395.2411
skew_mean_score <- skewness(num_data$mean_score, na.rm=TRUE)
skew_mean_score
## [1] -1.623193

Giải thích: skewness() đo độ lệch của phân phối dữ liệu.
- Skewness > 0: lệch phải (có nhiều giá trị nhỏ, ít giá trị cực lớn).
- Skewness < 0: lệch trái (nhiều giá trị cao, ít giá trị nhỏ).
Nhận xét: days_watched có skewness bằng 395.2411, cho thấy đa số người dùng chỉ xem ít anime, nhưng có một số ít người xem rất nhiều. Đối với mean_score, giá trị âm cho thấy phân phối lệch trái nhẹ. Phần lớn người dùng có xu hướng đánh giá cao, trong khi chỉ có một phần nhỏ người dùng cho điểm rất thấp.

3.2.12 Độ nhọn (Kurtosis)

kurt_days_watched <- kurtosis(num_data$days_watched, na.rm=TRUE)
kurt_days_watched
## [1] 175897.1
kurt_mean_score <- kurtosis(num_data$mean_score, na.rm=TRUE)
kurt_mean_score
## [1] 4.163564

Giải thích: kurtosis() đo mức độ tập trung của phân phối quanh trung bình.
- Giá trị > 3: phân phối nhọn (tập trung mạnh, nhiều outlier).
- Giá trị < 3: phân phối bẹt (phân tán đều hơn).
Nhận xét: Nếu days_watched có kurtosis cực kỳ cao, cho thấy đa số người dùng xem một lượng tương tự nhau, nhưng cũng tồn tại một số ít cá nhân có hành vi cực đoan (xem cực kỳ nhiều hoặc rất ít). Mean_score có kurtosis lớn hơn 3, điều đó cho thấy điểm đánh giá phân tán rộng, không có sự tập trung rõ ở mức trung bình — người dùng có gu anime rất khác nhau.

3.2.13 Hệ số biến thiên (Coefficient of Variation)

mean_days_watched <- mean(dataset$days_watched, na.rm = TRUE)
sd_days_watched   <- sd(dataset$days_watched, na.rm = TRUE)
cv_days_watched <- sd_days_watched / mean_days_watched
cv_days_watched
## [1] 4.375147
cv_mean_score <- sd(dataset$mean_score, na.rm = TRUE) / mean(dataset$mean_score, na.rm = TRUE)
cv_mean_score
## [1] 0.4393311

Giải thích: Hệ số biến thiên (CV) đo mức độ biến động tương đối so với trung bình, bằng tỷ lệ giữa độ lệch chuẩn và trung bình.
Nhận xét:
- CV của days_watched cao (lớn hơn 1), cho thấy mức biến động rất cao so với giá trị trung bình. nghĩa là mức độ chênh lệch trong thói quen xem anime giữa các người dùng là rất lớn tức có người xem cực nhiều, có người gần như không xem.
- Trong khi đó, CV của mean_score cho thấy độ đồng thuận trong đánh giá: giá trị này thấp hơn 1 phản ánh sự nhất quán trong nhận định về chất lượng anime; còn CV cao chỉ ra rằng đánh giá của người dùng rất khác nhau không xảy ra sự chênh lệch quá lớn trong cách chấm điểm, có thể do sự khác biệt về thể loại, độ nổi tiếng hoặc tiêu chí đánh giá cá nhân.

3.3 Tổng hợp và kiểm tra chất lượng dữ liệu

3.3.1 Tổng giá trị

sum_days_watched <- sum(num_data$days_watched, na.rm=TRUE)
sum_days_watched
## [1] 12114653
sum_mean_score <- sum(num_data$mean_score, na.rm=TRUE)
sum_mean_score
## [1] 1527711

Giải thích: Sử dụng hàm sum() để tính tổng giá trị của hai biến định lượng là days_watched (tổng số ngày người dùng xem anime) và mean_score (điểm trung bình đánh giá anime). Tham số na.rm=TRUE giúp bỏ qua các giá trị bị thiếu (NA) khi tính toán, đảm bảo kết quả chính xác.
Nhận xét:
- Tổng giá trị của days_watched cho thấy mức độ tổng thể mà toàn bộ người dùng trong mẫu dữ liệu đã dành thời gian cho việc xem anime. Giá trị này càng cao thể hiện mức độ tương tác lớn và thời gian đầu tư cho nội dung cao.
- Tổng của mean_score biểu thị tổng điểm đánh giá mà tất cả người dùng đã chấm. Chỉ số này giúp hiểu được quy mô tổng thể của dữ liệu đánh giá và có thể dùng để so sánh giữa các nhóm người dùng hoặc giai đoạn khác nhau.

3.3.2 Đếm số lượng quan sát hợp lệ (Valid Observations)

valid_days <- sum(!is.na(num_data$days_watched))
valid_days
## [1] 224383
valid_score <- sum(!is.na(num_data$mean_score))
valid_score
## [1] 224383

Giải thích: Hàm is.na() trả về giá trị TRUE nếu phần tử bị thiếu, và !is.na() đảo ngược để chọn những giá trị hợp lệ. Sau đó, sum() cộng tổng số giá trị TRUE, tức là tổng số quan sát không bị thiếu trong từng biến.
Nhận xét:
- Số lượng quan sát hợp lệ của days_watched và mean_score cho biết mức độ hoàn thiện của dữ liệu.
- Cả hai biến đều có 224383 số lượng quan sát hợp lệ bằng tổng số dòng của dataset cho thấy toàn bộ dữ liệu đầy đủ, không có giá trị NA.

3.3.3 Kiểm tra tổng số giá trị thiếu NA

na_total <- sum(is.na(num_data))
na_total
## [1] 0

Giải thích: Lệnh này tính tổng toàn bộ số lượng giá trị bị thiếu (NA) trong toàn bộ bảng dữ liệu định lượng num_data.
Nhận xét: Kết quả cho thấy dữ liệu còn bao nhiêu giá trị bị thiếu. Đây là bước quan trọng để đánh giá chất lượng dữ liệu đầu vào. Nếu số lượng NA cao, mô hình phân tích sau này có thể bị sai lệch, và cần phải xử lý bằng phương pháp thay thế trung vị, trung bình hoặc loại bỏ dòng. Trong trường hợp này, việc kiểm tra NA giúp đảm bảo dữ liệu đã được làm sạch ở bước 2 trước đó.

3.3.4 Hệ số tương quan giữa Days Watched và Mean Score

cor_days_score <- cor(num_data$days_watched, num_data$mean_score, use="complete.obs")
cor_days_score
## [1] 0.04657811

Giải thích: Hàm cor() tính hệ số tương quan Pearson giữa hai biến định lượng days_watched và mean_score. Tham số use=“complete.obs” đảm bảo chỉ tính toán với các quan sát đầy đủ (không chứa NA ở hai biến). Hệ số tương quan (r) dao động từ -1 đến 1:
- r > 0: Mối quan hệ thuận, khi số ngày xem tăng thì điểm đánh giá cũng có xu hướng tăng.
- r < 0: Mối quan hệ nghịch, khi xem nhiều ngày thì điểm đánh giá có xu hướng giảm.
- r ≈ 0: Không có mối liên hệ đáng kể.
Nhận xét: Kết quả cho thấy hệ số tương quan gần bằng 0 thể hiện mối liên hệ giữa số ngày xem anime và điểm đánh giá trung bình hầu như không đáng kể.

3.3.5 Ma trận tương quan giữa các biến định lượng

cor_matrix <- cor(num_data, use="complete.obs")
cor_matrix
##                  days_watched mean_score   watching  completed    on_hold
## days_watched       1.00000000 0.04657811 0.08909481 0.27191605 0.07455846
## mean_score         0.04657811 1.00000000 0.09358653 0.08625524 0.05174231
## watching           0.08909481 0.09358653 1.00000000 0.27563638 0.16701886
## completed          0.27191605 0.08625524 0.27563638 1.00000000 0.24370111
## on_hold            0.07455846 0.05174231 0.16701886 0.24370111 1.00000000
## dropped            0.07624655 0.02001563 0.09494424 0.26743060 0.20491943
## plan_to_watch      0.08851837 0.06120565 0.25272542 0.33204146 0.24186015
## total_entries      0.24775387 0.09734002 0.38814331 0.90558924 0.38102608
## rewatched          0.11922316 0.06025678 0.07110934 0.20963029 0.07515581
## episodes_watched   0.61279105 0.04543834 0.08219492 0.23970288 0.07015880
##                     dropped plan_to_watch total_entries  rewatched
## days_watched     0.07624655    0.08851837    0.24775387 0.11922316
## mean_score       0.02001563    0.06120565    0.09734002 0.06025678
## watching         0.09494424    0.25272542    0.38814331 0.07110934
## completed        0.26743060    0.33204146    0.90558924 0.20963029
## on_hold          0.20491943    0.24186015    0.38102608 0.07515581
## dropped          1.00000000    0.15864750    0.40469141 0.06463776
## plan_to_watch    0.15864750    1.00000000    0.66489377 0.08702700
## total_entries    0.40469141    0.66489377    1.00000000 0.20047691
## rewatched        0.06463776    0.08702700    0.20047691 1.00000000
## episodes_watched 0.06585478    0.07785181    0.21881104 0.12370288
##                  episodes_watched
## days_watched           0.61279105
## mean_score             0.04543834
## watching               0.08219492
## completed              0.23970288
## on_hold                0.07015880
## dropped                0.06585478
## plan_to_watch          0.07785181
## total_entries          0.21881104
## rewatched              0.12370288
## episodes_watched       1.00000000

Giải thích: Hàm cor() được áp dụng lên toàn bộ bảng dữ liệu định lượng num_data để tạo ma trận tương quan giữa tất cả các biến.
Nhận xét: Ma trận tương quan giúp ta quan sát mối quan hệ tuyến tính giữa từng cặp biến trong dataset. Thông qua ma trận này, có thể phát hiện biến nào có mối liên hệ mạnh, biến nào độc lập. Việc này rất hữu ích trong phân tích hồi quy hoặc xây dựng mô hình dự báo vì giúp tránh đa cộng tuyến.
Kết quả cho thấy một số cặp biến có mỗi tương quan dương mạnh:
- completed và total_entries (r = 0.9055): Đây là mối tương quan dương rất mạnh, gần như tuyến tính hoàn hảo. Nghĩa là số anime đã hoàn thành gần như tăng tương ứng với tổng số anime mà người dùng đã thêm vào danh sách.
- plan_to_watch và total_entries (r = 0.6648): Mối tương quan dương khá cao, cho thấy người dùng có nhiều anime đã xem thường cũng có nhiều anime dự định xem.

3.3.6 Thống kê mô tả tổng hợp bằng hàm describe()

describe(num_data)
##                  vars      n    mean       sd median trimmed     mad min
## days_watched        1 224383   53.99   236.22   29.2   39.20   40.18   0
## mean_score          2 224383    6.81     2.99    7.8    7.31    1.07   0
## watching            3 224383   10.18    28.13    4.0    5.64    5.93   0
## completed           4 224383  151.86   270.45   59.0   95.47   85.99   0
## on_hold             5 224383    7.98    30.34    1.0    3.47    1.48   0
## dropped             6 224383   10.39    51.04    1.0    3.90    1.48   0
## plan_to_watch       7 224383   42.11   141.99    7.0   18.36   10.38   0
## total_entries       8 224383  222.54   382.51   94.0  146.13  134.92   0
## rewatched           9 224383   10.45    48.08    0.0    2.91    0.00   0
## episodes_watched   10 224383 3440.74 16790.89 1748.0 2368.61 2407.74   0
##                        max     range   skew  kurtosis    se
## days_watched      105338.6  105338.6 395.24 175892.52  0.50
## mean_score            10.0      10.0  -1.62      1.16  0.01
## watching            2934.0    2934.0  25.29   1566.98  0.06
## completed          13226.0   13226.0   6.64    117.21  0.57
## on_hold             5167.0    5167.0  57.55   7229.67  0.06
## dropped            14341.0   14341.0 130.48  31514.33  0.11
## plan_to_watch      21804.0   21804.0  33.27   3166.77  0.30
## total_entries      24817.0   24817.0   8.80    278.84  0.81
## rewatched          13215.0   13215.0 102.21  25725.71  0.10
## episodes_watched 5433345.0 5433345.0 236.00  69141.40 35.45

Giải thích: Hàm describe() trong thư viện psych tạo ra bảng thống kê mô tả đầy đủ cho tất cả các biến định lượng, bao gồm số lượng quan sát, giá trị trung bình, độ lệch chuẩn, min, max, median, skewness, kurtosis,…
Nhận xét: Đây là bước tổng hợp toàn diện giúp ta nhìn nhanh toàn bộ đặc điểm của dữ liệu sau khi xử lý. Dựa vào bảng này, ta có thể đánh giá độ phân tán, xu hướng trung tâm, mức độ lệch, cũng như sự khác biệt giữa các biến. Đối với dữ liệu về người dùng anime, bảng này giúp nhận diện các hành vi nổi bật: ví dụ, người dùng trung bình xem bao nhiêu ngày, đánh giá trung bình bao nhiêu điểm, hay biến nào có mức độ dao động lớn nhất.

4. Trực quan hóa dữ liệu

4.1 Phân bố điểm trung bình theo giới tính

ggplot(dataset, aes(x = gender, y = mean_score, fill = gender)) +
  geom_boxplot(alpha = 0.7) +                  
  geom_jitter(color = "black", alpha = 0.2) +   
  stat_summary(fun = mean, geom = "point", color = "red", size = 3) + 
  labs(title = "Phân bố điểm trung bình theo giới tính",
       x = "Giới tính", y = "Điểm trung bình") +                      
  theme_minimal() +                             
  theme(legend.position = "none")

Giải thích: Biểu đồ sử dụng ggplot2 để thể hiện phân bố điểm trung bình (mean_score) theo giới tính (gender).
- geom_boxplot() hiển thị hộp dữ liệu thể hiện trung vị, tứ phân vị và ngoại lệ.
- geom_jitter() thêm các điểm rải rác để minh họa phân bố từng cá nhân.
- stat_summary(fun = mean) đánh dấu giá trị trung bình bằng điểm màu đỏ.
- labs() đặt tiêu đề và nhãn trục, theme_minimal() giúp biểu đồ rõ ràng, dễ đọc. - theme_minimal(): Tùy chỉnh giao diện, loại bỏ chi tiết thừa để biểu đồ sạch, hiện đại và dễ đọc.
Nhận xét: Biểu đồ thể hiện sự phân bố điểm trung bình của người dùng theo từng giới tính cho thấy có sự khác biệt nhất định giữa các nhóm. Các hộp trong biểu đồ biểu thị mức độ tập trung và phân tán của điểm số, trong đó nhóm có hộp nhỏ hơn thể hiện sự đồng nhất cao hơn trong cách cho điểm, còn nhóm có hộp rộng cho thấy sự đa dạng hơn trong hành vi đánh giá. Điểm tròn màu đỏ biểu diễn giá trị trung bình của mỗi nhóm, giúp ta dễ dàng nhận thấy nhóm nào có xu hướng đánh giá cao hơn hoặc thấp hơn trung bình chung.

4.2 Phân bố số ngày xem phim

ggplot(dataset, aes(x = days_watched)) +
  geom_histogram(binwidth = 50, fill = "skyblue", color = "black", alpha = 0.8) +
  geom_vline(aes(xintercept = mean(days_watched, na.rm = TRUE)), 
             color = "red", linetype = "dashed") +
  annotate("text", x = mean(dataset$days_watched, na.rm = TRUE) + 200, 
           y = 100, label = "Trung bình", vjust = -1, color = "red") +
  labs(title = "Phân bố số ngày xem phim", x = "Số ngày", y = "Tần suất") +
  xlim(0, 3000) +        
  theme_light()
## Warning: Removed 9 rows containing non-finite outside the scale range
## (`stat_bin()`).
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_bar()`).

Giải thích:
- geom_histogram(): Tạo biểu đồ cột thể hiện tần suất xuất hiện của biến days_watched trong từng khoảng (mỗi khoảng rộng 50 ngày). Màu cột xanh da trời giúp dễ quan sát, đường viền đen tạo độ tách biệt giữa các cột.
– geom_vline(): Thêm đường gạch đỏ (dashed line) biểu diễn giá trị trung bình của days_watched, giúp nhận biết vị trí của giá trị trung tâm.
– annotate(): Gắn nhãn “Trung bình” ngay vị trí đường đỏ, giúp người xem dễ hiểu ý nghĩa của đường đánh dấu này.
– labs(): Đặt tiêu đề và nhãn trục X, Y, giúp biểu đồ có ngữ cảnh rõ ràng và chuyên nghiệp.
- xlim(0, 3000): Giới hạn phần dữ liệu chính.
– theme_light(): Sử dụng giao diện sáng, nền trắng – giúp biểu đồ sạch, dễ nhìn.
Nhận xét: Biểu đồ histogram mô tả phân bố số ngày người dùng xem phim cho thấy phần lớn người dùng chỉ xem trong một khoảng thời gian ngắn, trong khi có một số ít người dành thời gian xem phim rất lớn. Phân phối dữ liệu thể hiện rõ xu hướng lệch phải, nghĩa là đa số tập trung ở các giá trị nhỏ, còn phần đuôi kéo dài về phía bên phải do ảnh hưởng của một số người dùng “xem nhiều bất thường”. Đường trung bình màu đỏ nằm khá xa về phía đuôi phân phối, điều này cho thấy giá trị trung bình bị chi phối bởi các trường hợp ngoại lệ. Vì vậy, trong trường hợp này, giá trị trung vị (median) sẽ phản ánh đúng hơn xu hướng chung của dữ liệu. Biểu đồ giúp ta nhận ra rằng hành vi xem phim của người dùng không đồng đều: có sự chênh lệch lớn giữa nhóm xem ít và nhóm xem nhiều.

4.3 Quan hệ giữa số ngày xem phim và số tập đã xem

ggplot(dataset, aes(x = days_watched, y = episodes_watched)) +
  geom_point(alpha = 0.4, color = "darkblue") +
  geom_smooth(method = "lm", color = "red", se = FALSE) +
  geom_density_2d(color = "gray50") +
  labs(title = "Mối quan hệ giữa số ngày xem và số tập đã xem",
       x = "Số ngày xem", y = "Số tập đã xem") +
  xlim(0, 5000) +          
  ylim(0, 200000) +         
  theme_bw()
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Removed 31 rows containing non-finite outside the scale range
## (`stat_smooth()`).
## Warning: Removed 31 rows containing non-finite outside the scale range
## (`stat_density2d()`).
## Warning: Removed 31 rows containing missing values or values outside the scale range
## (`geom_point()`).
## Warning: Removed 7 rows containing missing values or values outside the scale range
## (`geom_smooth()`).

Giải thích:
– geom_point(): Vẽ các điểm dữ liệu (scatter plot), mỗi điểm biểu diễn một người dùng với tọa độ gồm days_watched (trục X) và episodes_watched (trục Y).
+ alpha = 0.4 làm mờ để giảm chồng lấn điểm.
+ color = “darkblue” giúp điểm nổi bật, dễ quan sát.
– geom_smooth(method = “lm”): Thêm đường hồi quy tuyến tính màu đỏ, biểu diễn xu hướng quan hệ giữa hai biến.
+ method = “lm” nghĩa là dùng linear model (hồi quy tuyến tính).
+ se = FALSE tắt vùng tin cậy (confidence interval) để đồ thị gọn hơn.
– geom_density_2d(): Vẽ đường đồng mật độ (contour lines) thể hiện vùng có mật độ điểm cao, giúp thấy khu vực tập trung dữ liệu (nhiều người dùng có hành vi tương tự).
– labs(): Thêm tiêu đề và nhãn trục X, Y để biểu đồ rõ nghĩa: “Mối quan hệ giữa số ngày xem và số tập đã xem”.
- xlim(0, 5000) +
ylim(0, 200000) + : Giới hạn phần chính.
– theme_bw(): Dùng giao diện trắng đen (black & white theme) cho phong cách chuyên nghiệp, dễ đọc và làm nổi bật điểm dữ liệu.
Nhận xét: Biểu đồ thể hiện mối quan hệ giữa số ngày xem phim và số tập đã xem của người dùng cho thấy xu hướng tuyến tính rõ rệt và dương tính mạnh. Đường hồi quy màu đỏ thể hiện rằng khi số ngày xem phim tăng, số tập phim đã xem cũng tăng gần như tỷ lệ thuận, chứng tỏ người dùng có xu hướng duy trì mức độ xem ổn định theo thời gian. Các điểm dữ liệu phân bố dày đặc ở góc dưới bên trái cho thấy phần lớn người dùng chỉ xem trong thời gian ngắn và với số lượng tập hạn chế, trong khi một số ít điểm nằm xa về phía bên phải biểu diễn những người có tần suất xem rất cao – đây có thể là những người hâm mộ hoặc “nghiện phim”. Sự xuất hiện của các cụm điểm đậm ở một số vùng phản ánh hành vi xem phổ biến.

4.4 Biểu đồ cột so sánh số anime đang xem theo giới tính

ggplot(dataset, aes(x = gender, y = watching, fill = gender)) +
  geom_col(position = "dodge") +                     
  geom_text(aes(label = watching), vjust = -0.5) +   
  labs(title = "Số anime đang xem theo giới tính", 
       x = "Giới tính", y = "Số lượng anime") +     
  scale_fill_brewer(palette = "Set2") +              
  theme_classic()       

Giải thích:
- geom_col(): Vẽ biểu đồ cột thể hiện số anime đang xem (watching) theo giới tính (gender), với các cột đặt cạnh nhau (position = “dodge”).
- geom_text(): Hiển thị giá trị số lượng trên đầu mỗi cột giúp dễ so sánh.
- labs() – Thêm tiêu đề và nhãn trục X, Y: “Số anime đang xem theo giới tính”.
- scale_fill_brewer(palette = “Set2”): Dùng bảng màu tươi sáng, phân biệt rõ giới tính.
- theme_classic(): Dùng giao diện đơn giản, làm nổi bật dữ liệu.
Nhận xét: Biểu đồ cột so sánh số lượng anime đang xem theo giới tính cho thấy có sự khác biệt nhẹ giữa các nhóm. Trong đó, nam giới có xu hướng theo dõi nhiều anime hơn một chút so với nữ giới, trong khi nhóm người dùng thuộc giới tính khác (“Non-binary”) có số lượng thấp hơn đáng kể – điều này có thể do quy mô nhóm nhỏ hoặc mức độ tham gia cộng đồng thấp hơn. Các cột được tô màu khác nhau giúp làm nổi bật sự khác biệt trực quan giữa các nhóm giới, đồng thời giá trị hiển thị trên đỉnh mỗi cột giúp người đọc dễ dàng so sánh chính xác.

4.5 Điểm trung bình theo số lần xem lại (rewatched)

ggplot(dataset, aes(x = as.factor(rewatched), y = mean_score, fill = as.factor(rewatched))) +
  geom_boxplot() +                                   
  geom_jitter(alpha = 0.3) +                         
  stat_summary(fun = mean, geom = "line", aes(group=1), color="red") + 
  labs(title = "Điểm trung bình theo số lần xem lại",
       x = "Số lần xem lại", y = "Điểm trung bình") +                
  theme_minimal() +                                
  theme(legend.position = "none")

Giải thích:
- geom_boxplot(): Vẽ biểu đồ hộp thể hiện sự phân bố của mean_score (điểm trung bình) theo rewatched (số lần xem lại). Hộp thể hiện trung vị, tứ phân vị và các giá trị ngoại lai.
- geom_jitter(alpha = 0.3): Thêm các điểm dữ liệu rải rác để hiển thị phân tán thực tế, tránh chồng lấn.
- stat_summary(fun = mean, geom = “line”, aes(group=1), color=“red”): Vẽ đường nối các giá trị trung bình giữa các nhóm, giúp quan sát xu hướng thay đổi điểm trung bình theo số lần xem lại.
- labs(): Thêm tiêu đề và nhãn trục, giúp người xem dễ hiểu nội dung biểu đồ.
- theme_minimal(): Dùng giao diện tối giản, loại bỏ chi tiết thừa để tập trung vào dữ liệu (ẩn chú thích bằng theme(legend.position=“none”)).
Nhận xét: Biểu đồ thể hiện mối quan hệ giữa số lần xem lại anime và điểm trung bình mà người xem đánh giá. Có thể thấy, khi số lần xem lại tăng lên, điểm trung bình có xu hướng duy trì ở mức ổn định và cao hơn trung bình chung. Điều này cho thấy những bộ anime được xem lại nhiều lần thường là các tác phẩm có chất lượng nội dung cao, dễ để lại ấn tượng mạnh hoặc có yếu tố cảm xúc khiến người xem muốn thưởng thức lại. Tổng thể, biểu đồ phản ánh rằng tần suất xem lại có tương quan tích cực với mức độ yêu thích của khán giả.

4.6 Tương quan giữa tổng số anime và điểm trung bình

ggplot(dataset, aes(x = total_entries, y = mean_score)) +
  geom_point(alpha = 0.4, color = "orange") +       
  geom_density_2d(color = "blue") +                  
  geom_smooth(method = "lm", color = "red", se = FALSE) + 
  labs(title = "Tương quan giữa tổng số anime và điểm trung bình") + 
  theme_light() +                                     
  theme(plot.title = element_text(face = "bold"))
## `geom_smooth()` using formula = 'y ~ x'

Giải thích:
- geom_point(alpha = 0.4, color = “orange”): Vẽ các điểm phân tán thể hiện mối quan hệ giữa tổng số anime mà người dùng đã thêm vào danh sách (total_entries) và điểm trung bình (mean_score) mà họ đánh giá. Mỗi điểm đại diện cho một người dùng.
- geom_density_2d(color = “blue”): Thêm đường đồng mật độ 2D, thể hiện vùng tập trung cao của các điểm dữ liệu, giúp xác định khu vực có nhiều người dùng có đặc điểm tương tự.
- geom_smooth(method = “lm”, color = “red”, se = FALSE): Thêm đường hồi quy tuyến tính, minh họa xu hướng tổng thể giữa hai biến. Màu đỏ nổi bật giúp dễ nhận thấy mối tương quan (tăng hoặc giảm).
- labs(title = “Tương quan giữa tổng số anime và điểm trung bình”): Thêm tiêu đề giúp người xem hiểu rõ nội dung biểu đồ.
- theme_light() và theme(plot.title = element_text(face = “bold”)): Tạo phong cách sáng, đơn giản, đồng thời làm nổi bật tiêu đề để biểu đồ rõ ràng và dễ đọc.
Nhận xét: Biểu đồ này mô tả mối quan hệ giữa tổng số anime mà người dùng đã xem và điểm trung bình họ chấm. Dễ nhận thấy xu hướng đường hồi quy (màu đỏ) cho thấy người xem càng có kinh nghiệm xem nhiều anime, điểm trung bình họ chấm càng có xu hướng giảm nhẹ. Điều này phản ánh một thực tế phổ biến: những người đã xem nhiều anime hơn thường khó tính hơn trong việc đánh giá, bởi họ có chuẩn so sánh rộng hơn và dễ nhận ra điểm yếu trong từng tác phẩm. Ngược lại, những người xem ít anime thường có xu hướng chấm điểm cao hơn, do họ dễ bị ấn tượng bởi các yếu tố mới lạ. Như vậy, biểu đồ này làm nổi bật mối quan hệ nghịch nhẹ giữa số lượng anime đã xem và mức độ hài lòng trung bình.

4.7 Phân bố điểm trung bình theo giới tính (violin)

ggplot(dataset, aes(x = gender, y = mean_score, fill = gender)) +
  geom_violin(alpha = 0.7) +                         
  geom_boxplot(width = 0.1, fill = "white") +        
  geom_jitter(width = 0.2, alpha = 0.3) +             
  labs(title = "Phân bố điểm trung bình theo giới tính") +           
  theme_bw() +                                       
  theme(legend.position = "none")

Giải thích:
- geom_violin(alpha = 0.7): Vẽ biểu đồ violin, thể hiện phân bố xác suất của điểm trung bình (mean_score) theo giới tính (gender). Phần dày của hình violin biểu thị vùng có nhiều quan sát tập trung.
- geom_boxplot(width = 0.1, fill = “white”): Thêm biểu đồ hộp (boxplot) bên trong mỗi violin để hiển thị thống kê tóm tắt như trung vị, tứ phân vị và giá trị ngoại lai.
- geom_jitter(width = 0.2, alpha = 0.3): Hiển thị các điểm dữ liệu riêng lẻ bằng cách rải nhẹ theo chiều ngang để tránh chồng chéo, giúp nhìn rõ mật độ phân bố thực tế.
- labs(title = “Phân bố điểm trung bình theo giới tính”): Thêm tiêu đề mô tả rõ nội dung biểu đồ.
- theme_bw() + theme(legend.position = “none”): Dùng giao diện nền trắng (black and white) giúp biểu đồ gọn gàng, tập trung vào dữ liệu, đồng thời ẩn chú giải vì màu sắc đã thể hiện rõ giới tính.
Nhận xét: Biểu đồ violin cho thấy sự khác biệt trong phân bố điểm trung bình giữa các nhóm giới tính: Female, Male và Non-Binary.
Nhìn chung, cả ba nhóm có xu hướng tập trung điểm trung bình trong khoảng 6.5 đến 8 điểm, cho thấy mức độ hài lòng tương đối đồng nhất giữa các giới. Tuy nhiên, có thể nhận thấy nhóm Non-Binary có độ phân tán rộng hơn, biểu hiện qua phần thân violin dài hơn — điều này có nghĩa là ý kiến đánh giá trong nhóm này đa dạng và không đồng nhất như hai nhóm còn lại.
Ngược lại, nhóm Female và Male có phân bố tương đối đối xứng và tập trung, cho thấy mức độ đồng thuận cao hơn trong việc đánh giá anime. Tổng thể, biểu đồ chỉ ra rằng giới tính không phải là yếu tố tạo ra sự chênh lệch đáng kể trong điểm trung bình, nhưng có sự khác biệt nhỏ về mức độ phân tán và độ ổn định trong đánh giá.

4.8 Quan hệ giữa số tập và ngày xem theo số lần xem lại

ggplot(subset(dataset, episodes_watched < 500000 & days_watched < 10000),
       aes(x = episodes_watched, y = days_watched, color = rewatched)) +
  geom_point(alpha = 0.6) +
  geom_smooth(method = "lm", se = FALSE, color = "black") +
  scale_color_gradient(low = "blue", high = "red") +
  labs(title = "Quan hệ giữa số tập và số ngày xem theo số lần xem lại (loại bỏ ngoại lệ)",
       x = "Số tập đã xem", y = "Số ngày xem") +
  theme_minimal()
## `geom_smooth()` using formula = 'y ~ x'

Giải thích:
- geom_point(alpha = 0.6): Vẽ biểu đồ phân tán (scatter plot), hiển thị mối quan hệ giữa số tập đã xem (episodes_watched) và số ngày xem (days_watched). Mỗi điểm đại diện cho một người dùng. Độ trong suốt (alpha = 0.6) giúp tránh chồng chéo khi dữ liệu dày đặc.
- geom_smooth(method = “lm”, se = FALSE, color = “black”): Thêm đường hồi quy tuyến tính (linear model) để thể hiện xu hướng chung giữa hai biến. Đường này giúp ta nhận biết liệu người xem nhiều tập có xu hướng dành nhiều ngày hơn để xem không.
- scale_color_gradient(low = “blue”, high = “red”): Tô màu dựa trên biến rewatched (số lần xem lại): Màu xanh thể hiện ít hoặc không xem lại. Màu đỏ thể hiện xem lại nhiều lần. Việc này giúp làm nổi bật sự khác biệt trong hành vi giữa những người xem lại ít và nhiều.
- labs(title = “Quan hệ giữa số tập và ngày xem theo số lần xem lại”): Đặt tiêu đề biểu đồ, mô tả rõ ràng nội dung và biến được phân tích.
- theme_minimal(): Áp dụng giao diện tối giản, giúp biểu đồ sáng sủa, tập trung vào dữ liệu và tránh yếu tố gây nhiễu thị giác.
Nhận xét: Biểu đồ thể hiện mối quan hệ giữa số tập anime đã xem (episodes_watched) và số ngày xem (days_watched), được tô màu theo số lần xem lại (rewatched). Quan sát thấy phần lớn các điểm dữ liệu tập trung theo đường xu hướng tăng, cho thấy rằng người xem càng xem nhiều tập thì tổng số ngày xem càng tăng theo tỉ lệ thuận — điều này hoàn toàn hợp lý vì người xem cần nhiều thời gian hơn để hoàn thành nhiều tập phim hơn. Đáng chú ý, các điểm có màu đỏ (tức là rewatched cao) thường nằm ở khu vực có số tập và số ngày cao hơn trung bình, cho thấy những người có thói quen xem lại anime cũng là những người xem nhiều và đầu tư thời gian dài hơn cho sở thích của mình. Tóm lại, biểu đồ phản ánh mối quan hệ tích cực giữa khối lượng xem anime và thời gian dành cho nó, đồng thời nhấn mạnh rằng sự đam mê và mức độ gắn bó với anime có liên hệ chặt chẽ với thói quen xem lại.

4.9 Điểm trung bình theo số anime đã hoàn thành

ggplot(dataset, aes(x = completed, y = mean_score)) +
  geom_point(alpha = 0.4, color = "steelblue") +     
  geom_smooth(method = "loess", color = "red") +      
  geom_density_2d(color = "gray50") +                 
  labs(title = "Điểm trung bình theo số anime đã hoàn thành") +       
  theme_classic() +                                  
  theme(plot.title = element_text(size = 12, face = "bold"))
## `geom_smooth()` using formula = 'y ~ x'
## Warning: Failed to fit group -1.
## Caused by error in `predLoess()`:
## ! workspace required (75523896008) is too large probably because of setting 'se = TRUE'.

Giải thích:
- geom_point(alpha = 0.4, color = “steelblue”): Vẽ biểu đồ phân tán (scatter plot), thể hiện từng quan sát giữa hai biến:
+ Trục X: completed (số anime đã hoàn thành).
+ Trục Y: mean_score (điểm trung bình người dùng đánh giá).
Mỗi điểm đại diện cho một người dùng. Độ trong suốt alpha = 0.4 giúp tránh chồng lấn khi dữ liệu dày.
- geom_smooth(method = “loess”, color = “red”): Thêm đường xu hướng (LOESS curve) – một phương pháp hồi quy phi tuyến tính, thể hiện xu hướng thay đổi của điểm trung bình theo số anime đã hoàn thành. Đường màu đỏ mô tả mối quan hệ trơn tru, không nhất thiết tuyến tính giữa hai biến.
- geom_density_2d(color = “gray50”): Thêm đường mật độ 2 chiều (contour lines) để biểu diễn khu vực tập trung nhiều điểm dữ liệu. Những vùng có nhiều đường bao dày hơn thể hiện mật độ người xem cao hơn, tức là nhiều người có cùng hành vi xem và điểm số tương tự.
- labs(title = “Điểm trung bình theo số anime đã hoàn thành”): Đặt tiêu đề biểu đồ, giúp người đọc hiểu rõ nội dung biểu diễn là mối liên hệ giữa số anime hoàn thành và điểm đánh giá trung bình.
- theme_classic() + theme(plot.title = element_text(size = 12, face = “bold”)): Áp dụng giao diện cổ điển (classic) với nền trắng và đường trục rõ nét, đồng thời làm nổi bật tiêu đề bằng cỡ chữ lớn và in đậm để tăng tính trực quan.
Nhận xét: Biểu đồ thể hiện mối quan hệ giữa số anime đã hoàn thành (completed) và điểm trung bình đánh giá (mean_score). Quan sát cho thấy có xu hướng tăng nhẹ: những người xem và hoàn thành nhiều anime hơn thường có xu hướng đưa ra điểm trung bình cao hơn, phản ánh rằng họ là nhóm người xem có kinh nghiệm và chọn lọc nội dung kỹ hơn. Tuy nhiên, mật độ điểm dữ liệu dày ở vùng số anime thấp cho thấy phần lớn người dùng mới hoặc người xem thông thường vẫn chiếm tỷ lệ lớn. Tổng thể, biểu đồ gợi ý rằng mức độ trải nghiệm có thể ảnh hưởng tích cực đến đánh giá anime.

4.10 So sánh tổng số các trạng thái xem anime

dataset %>%
  summarise(across(c(watching, completed, on_hold, dropped, plan_to_watch),
                   sum, na.rm = TRUE)) %>%
  pivot_longer(everything(), names_to = "status", values_to = "count") %>%
  ggplot(aes(x = status, y = count, fill = status)) +
  geom_col() +                                        
  geom_text(aes(label = count), vjust = -0.5) +       
  labs(title = "So sánh các trạng thái xem anime", 
       x = "Trạng thái", y = "Số lượng") +            
  scale_fill_brewer(palette = "Set3") +               
  theme_minimal()          
## Warning: There was 1 warning in `summarise()`.
## ℹ In argument: `across(...)`.
## Caused by warning:
## ! The `...` argument of `across()` is deprecated as of dplyr 1.1.0.
## Supply arguments directly to `.fns` through an anonymous function instead.
## 
##   # Previously
##   across(a:b, mean, na.rm = TRUE)
## 
##   # Now
##   across(a:b, \(x) mean(x, na.rm = TRUE))

Giải thích:
- %>%: là toán tử pipe, nghĩa là “chuyển kết quả của bước trước sang bước sau”.
- summarise(): tạo ra bảng tóm tắt.
- across(): áp dụng cùng một hàm (sum) lên nhiều cột cùng lúc.
- c(watching, completed, on_hold, dropped, plan_to_watch): đây là 5 cột thể hiện các trạng thái xem anime.
- sum(…, na.rm = TRUE): tính tổng số lượng anime cho từng trạng thái, bỏ qua giá trị NA.
- pivot_longer() biến đổi dữ liệu từ 5 cột thành 2 cột mới:
+ status: chứa tên trạng thái (watching, completed, …)
+ count: chứa giá trị tổng tương ứng.
- ggplot(aes(…)): khởi tạo biểu đồ, gán biến trục x, y và màu (fill).
- geom_col(): vẽ biểu đồ cột (height = count cho từng status).
- labs() giúp thêm tiêu đề chính, và tên trục X/Y cho biểu đồ.
- scale_fill_brewer(palette = “Set3”): chọn bảng màu “Set3” — các màu nhạt, dễ phân biệt.
- theme_minimal(): giao diện nhẹ nhàng, ít đường kẻ — giúp nổi bật dữ liệu.
- theme(plot.title = …): in đậm tiêu đề và chỉnh cỡ chữ.
Nhận xét: Biểu đồ cột cho thấy sự khác biệt rõ rệt trong số lượng anime ở các trạng thái xem khác nhau: “Completed”, “Watching”, “On-Hold”, “Dropped” và “Plan to Watch”. Kết quả minh họa rằng đa số người dùng có xu hướng hoàn thành hoặc lên kế hoạch xem nhiều anime, trong khi tỷ lệ anime bị “bỏ dở” hoặc “tạm dừng” thấp hơn. Điều này phản ánh sự gắn bó và kiên trì của cộng đồng người xem, đồng thời cho thấy rằng phần lớn người dùng có mục tiêu theo đuổi trọn vẹn bộ anime hơn là bỏ ngang.

4.11 Histogram số anime đang tạm dừng (on_hold)

ggplot(dataset, aes(x = on_hold)) +
  geom_histogram(binwidth = 2, fill = "gold", color = "black", alpha = 0.7) +    
  geom_vline(aes(xintercept = mean(on_hold, na.rm = TRUE)), 
             color = "red", linetype = "dashed") + 
  geom_density(aes(y = ..count.. * 3), color = "brown") +                            
  labs(title = "Phân bố số anime đang tạm dừng", 
       x = "Số anime on-hold", y = "Tần suất") +       
  xlim(0, 100) +        
  theme_light()
## Warning: The dot-dot notation (`..count..`) was deprecated in ggplot2 3.4.0.
## ℹ Please use `after_stat(count)` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
## Warning: Removed 1808 rows containing non-finite outside the scale range
## (`stat_bin()`).
## Warning: Removed 1808 rows containing non-finite outside the scale range
## (`stat_density()`).
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_bar()`).

Giải thích:
- ggplot(dataset, aes(x = on_hold)): Khởi tạo biểu đồ với trục hoành là số lượng anime đang tạm dừng (on_hold).
- geom_histogram(…): Vẽ biểu đồ tần suất (histogram) để hiển thị phân bố số anime đang bị tạm dừng.
binwidth = 2: mỗi cột đại diện cho nhóm 2 đơn vị. Và fill = “gold”, color = “black”, alpha = 0.7: chọn màu vàng nhạt, viền đen, độ trong suốt 70%.
- geom_vline(…): Thêm đường thẳng đứng màu đỏ nét đứt biểu diễn giá trị trung bình của biến on_hold. Giúp người xem dễ dàng nhận biết mức trung bình trên biểu đồ.
- geom_density(aes(y = ..count..*3), color = “brown”): Vẽ đường mật độ (density curve) chồng lên histogram, mô tả xu hướng phân bố mượt mà. Nhân hệ số 3 để điều chỉnh tỷ lệ y cho khớp với histogram.
Màu nâu giúp dễ phân biệt với các lớp khác.
- labs(…): Thêm tiêu đề và nhãn trục, giúp biểu đồ rõ ràng, có ngữ nghĩa.
- xlim(0, 100): Giới hạn phần dữ liệu chính.
- theme_light(): Dùng giao diện sáng nhẹ, dễ đọc.
Nhận xét: Biểu đồ histogram thể hiện phân bố số lượng anime đang ở trạng thái “On-Hold” của người xem. Dễ thấy rằng đa số người dùng chỉ có một vài anime bị tạm dừng, trong khi số lượng người có danh sách “On-Hold” dài là rất ít. Đường trung bình được thêm vào giúp xác định mức trung bình của dữ liệu, cho thấy phần lớn người xem chỉ tạm ngừng ở mức độ thấp, có thể do họ chuyển sang xem các anime mới hoặc đợi phần tiếp theo phát hành. Điều này thể hiện mức độ duy trì mối quan tâm cao của khán giả đối với các bộ anime đang theo dõi.

4.12 Histogram số anime bị bỏ (dropped)

ggplot(dataset, aes(x = dropped)) +
  geom_histogram(binwidth = 2, fill = "salmon", color = "black", alpha = 0.7) +
  geom_vline(aes(xintercept = mean(dropped, na.rm = TRUE)),
             color = "blue", linetype = "dotted") +
  annotate("text", x = mean(dataset$dropped, na.rm = TRUE) + 3, y = 150,
           label = "Mean", color = "blue") +
  labs(title = "Phân bố số anime bị bỏ dở",
       x = "Số anime bị bỏ", y = "Tần suất") +
  xlim(0, 100) +
  theme_bw()
## Warning: Removed 3614 rows containing non-finite outside the scale range
## (`stat_bin()`).
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_bar()`).

Giải thích:
- ggplot(dataset, aes(x = dropped)): Khởi tạo biểu đồ với dữ liệu từ dataset, trong đó biến dropped được chọn làm trục hoành. Biến này biểu thị số lượng anime mà người dùng đã bỏ dở.
- geom_histogram(binwidth = 2, fill = “salmon”, color = “black”, alpha = 0.7): Vẽ biểu đồ tần suất (histogram) mô tả phân bố của biến dropped.
+ binwidth = 2: mỗi cột đại diện cho một khoảng 2 đơn vị số anime bị bỏ dở.
+ fill = “salmon”: tô màu hồng nhạt cho các cột, giúp biểu đồ dễ nhìn.
+ color = “black”: viền đen bao quanh từng cột, làm nổi bật ranh giới giữa các khoảng.
+ alpha = 0.7: tăng độ trong suốt để biểu đồ mềm mại, trực quan hơn.
- geom_vline(aes(xintercept = mean(dropped, na.rm = TRUE)), color = “blue”, linetype = “dotted”): Thêm đường thẳng đứng tại vị trí giá trị trung bình của biến dropped.
+ color = “blue” giúp đường trung bình nổi bật.
+ linetype = “dotted” (nét chấm chấm) dùng để phân biệt với đường trục chính, tăng tính thẩm mỹ.
- annotate(“text”, x = mean(dataset$dropped, na.rm = TRUE) + 3, y = 150, label = “Mean”, color = “blue”): Thêm nhãn chữ “Mean” gần đường trung bình để người xem dễ nhận biết.
- labs(title = “Phân bố số anime bị bỏ dở”, x = “Số anime bị bỏ”, y = “Tần suất”): Đặt tiêu đề và nhãn trục cho biểu đồ.
+ Tiêu đề “Phân bố số anime bị bỏ dở” giúp người đọc nắm được ý nghĩa biểu đồ.
+ Trục X là số anime bị bỏ, trục Y là tần suất xuất hiện của từng giá trị.
- xlim(0, 100): Giới hạn trục X để dễ thấy phần chính.
- theme_bw(): Áp dụng giao diện nền trắng – kiểu hiển thị đơn giản, rõ ràng, giúp biểu đồ dễ đọc và phù hợp khi in trong báo cáo học thuật.
Nhận xét: Biểu đồ này cho thấy phân bố số lượng anime bị bỏ dở trong cộng đồng người xem. Phần lớn các giá trị tập trung ở mức thấp, thể hiện rằng đa số người dùng ít khi bỏ dở anime, trong khi chỉ có một nhóm nhỏ có số lượng “Dropped” cao hơn. Việc thêm đường trung bình (và nhãn “Mean”) giúp dễ dàng nhận thấy mức bỏ dở trung bình rất thấp, phản ánh rằng phần lớn khán giả có thói quen kiên nhẫn và hoàn thành bộ phim trước khi đưa ra đánh giá. Như vậy, đây là tín hiệu tích cực về mức độ yêu thích và cam kết của người xem đối với anime.

4.13 Histogram số anime lên kế hoạch xem (plan_to_watch)

ggplot(dataset, aes(x = plan_to_watch)) +
  geom_histogram(binwidth = 10, fill = "deepskyblue", color = "black", alpha = 0.7) + 
  geom_vline(aes(xintercept = mean(plan_to_watch, na.rm = TRUE)), 
             color = "red", linetype = "dashed") + 
  geom_density(aes(y = ..count.. * 2), color = "blue") +                 
  labs(title = "Phân bố số anime dự định xem", 
       x = "Số anime plan_to_watch", y = "Tần suất") + 
  xlim(0, 500) +  
  theme_minimal()
## Warning: Removed 2168 rows containing non-finite outside the scale range
## (`stat_bin()`).
## Warning: Removed 2168 rows containing non-finite outside the scale range
## (`stat_density()`).
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_bar()`).

Giải thích:
- ggplot(dataset, aes(x = plan_to_watch)): Khởi tạo biểu đồ từ bộ dữ liệu dataset, trong đó biến plan_to_watch được đặt ở trục hoành. Biến này thể hiện số lượng anime mà người dùng có kế hoạch xem trong tương lai, phản ánh ý định hoặc sở thích tiềm năng của họ.
- geom_histogram(binwidth = 10, fill = “deepskyblue”, color = “black”, alpha = 0.7): Vẽ biểu đồ tần suất (histogram) biểu diễn phân bố của biến plan_to_watch. + binwidth = 10: mỗi cột biểu đồ đại diện cho một khoảng 10 anime dự định xem, giúp dữ liệu mượt và dễ quan sát hơn.
+ fill = “deepskyblue”: sử dụng màu xanh da trời tươi để tăng tính trực quan và gợi cảm giác nhẹ nhàng.
+ color = “black”: viền đen quanh từng cột giúp các khoảng phân bố rõ ràng.
+ alpha = 0.7: làm các cột hơi trong suốt, tạo cảm giác mềm mại và chuyên nghiệp.
- geom_vline(aes(xintercept = mean(plan_to_watch, na.rm = TRUE)), color = “red”, linetype = “dashed”): Thêm đường thẳng đứng biểu diễn giá trị trung bình của số anime dự định xem.
+ color = “red”: màu đỏ nổi bật, giúp người đọc dễ dàng nhận biết vị trí trung bình.
+ linetype = “dashed”: sử dụng nét đứt để phân biệt đường trung bình với đường trục, đồng thời tăng tính thẩm mỹ cho biểu đồ.
- geom_density(aes(y = ..count..2), color = “blue”): Thêm đường mật độ (density curve) chồng lên biểu đồ tần suất, giúp biểu diễn dạng phân bố trơn của dữ liệu.
+ aes(y = ..count..
2): điều chỉnh tỷ lệ trục tung để đường mật độ khớp với chiều cao của các cột histogram, đảm bảo hai lớp hiển thị tương thích.
+ color = “blue”: tô đường mật độ màu xanh lam, tạo cảm giác cân đối với màu của cột histogram và dễ nhận diện.
- labs(title = “Phân bố số anime dự định xem”, x = “Số anime plan_to_watch”, y = “Tần suất”): Đặt tiêu đề và nhãn cho các trục của biểu đồ.
+ Tiêu đề “Phân bố số anime dự định xem” giúp người đọc hiểu rõ nội dung biểu đồ.
+ Trục X biểu diễn số lượng anime mà người dùng dự định xem, trục Y thể hiện tần suất xuất hiện của các giá trị này trong dữ liệu.
- xlim(0, 500): Giới hạn trục X để thấy rõ vùng phổ biến.
- theme_minimal(): Áp dụng giao diện nền tối giản, loại bỏ các chi tiết không cần thiết (đường viền, nền xám…), giúp tập trung sự chú ý vào các yếu tố dữ liệu chính và tạo cảm giác hiện đại, tinh gọn cho biểu đồ.
Nhận xét: Biểu đồ thể hiện số lượng anime mà người xem dự định sẽ xem trong tương lai (Plan to Watch). Kết quả cho thấy phần lớn người dùng có danh sách “Plan to Watch” khá dài, thể hiện sự quan tâm và nhu cầu khám phá anime mới liên tục. Đường density và đường trung bình giúp minh họa rằng có xu hướng nghiêng phải (right-skewed), nghĩa là vẫn có một số người dùng cực kỳ năng động với danh sách dự định dài vượt trội. Biểu đồ này phản ánh sự đam mê và hứng thú mạnh mẽ của cộng đồng anime, với xu hướng liên tục cập nhật và mở rộng trải nghiệm giải trí.

4.14 Histogram tổng số anime (total_entries)

ggplot(dataset, aes(x = total_entries)) +
  geom_histogram(binwidth = 10, fill = "violet", color = "black", alpha = 0.7) + 
  geom_vline(aes(xintercept = median(total_entries, na.rm = TRUE)), 
             color = "red", linetype = "dashed") + 
  geom_density(aes(y = ..count.. * 3), color = "darkblue") +                     
  labs(title = "Phân bố tổng số anime của người dùng", 
       x = "Tổng số anime", y = "Tần suất") + 
  xlim(0, 1000) +    
  theme_classic()   
## Warning: Removed 8186 rows containing non-finite outside the scale range
## (`stat_bin()`).
## Warning: Removed 8186 rows containing non-finite outside the scale range
## (`stat_density()`).
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_bar()`).

Giải thích:
- geom_histogram(binwidth = 10, fill = “violet”, color = “black”, alpha = 0.7): Vẽ biểu đồ tần suất (histogram) mô tả phân bố của biến total_entries – tức tổng số anime mà mỗi người dùng đã từng tương tác (bao gồm xem, lên kế hoạch, bỏ dở, v.v.). Các cột có màu tím (violet), đường viền đen giúp dễ phân biệt, và độ trong suốt alpha = 0.7 tạo cảm giác nhẹ, tránh biểu đồ bị quá đặc. Mỗi cột biểu diễn số lượng người dùng nằm trong từng khoảng giá trị tổng số anime.
- geom_vline(aes(xintercept = median(total_entries, na.rm = TRUE)), color = “red”, linetype = “dashed”): Thêm một đường thẳng đứng tại vị trí trung vị của tổng số anime. Đường màu đỏ, nét gạch (dashed) giúp dễ nhận biết vị trí giá trị trung vị – tức mức mà 50% người dùng có tổng số anime thấp hơn và 50% cao hơn.
- geom_density(aes(y = ..count.. * 3), color = “darkblue”): Chồng thêm đường mật độ (density curve) để biểu diễn dạng phân bố mượt hơn của dữ liệu. Đường màu xanh đậm cho thấy xu hướng tập trung hay phân tán của người dùng theo tổng số anime. Hệ số nhân (×3) giúp đường mật độ tỷ lệ thuận với trục tần suất, dễ so sánh trực quan với các cột histogram.
- labs(title = “Phân bố tổng số anime của người dùng”, x = “Tổng số anime”, y = “Tần suất”): Đặt tiêu đề và nhãn cho các trục, giúp người đọc dễ hiểu rằng biểu đồ đang mô tả sự phân bố của tổng số anime theo từng người dùng trong bộ dữ liệu.
- xlim(0, 1000): Giới hạn trục hoành từ 0 đến 1000 nhằm loại bỏ các giá trị ngoại lai (outliers) và giúp biểu đồ trực quan, tập trung hơn vào vùng dữ liệu chính.
- theme_classic(): Áp dụng giao diện cổ điển với nền trắng và khung trục rõ ràng, giúp biểu đồ gọn gàng, dễ đọc và mang phong cách học thuật.
Nhận xét: Biểu đồ này thể hiện phân bố tổng số anime mà người dùng đã tương tác (bao gồm các trạng thái như xem, đang xem, hoặc đã hoàn thành). Phần lớn người dùng có tổng số anime ở mức trung bình hoặc thấp, thể hiện qua mật độ tập trung cao ở vùng đầu của trục hoành. Đường trung bình màu đỏ cho thấy số lượng anime trung bình mỗi người theo dõi không quá lớn, trong khi một số ít người dùng có giá trị cao hơn hẳn, cho thấy sự đa dạng trong mức độ tham gia của cộng đồng. Nhìn chung, biểu đồ phản ánh rằng phần đông người dùng có xu hướng theo dõi anime ở mức vừa phải, trong khi một nhóm nhỏ lại thể hiện độ đam mê cao hơn hẳn.

4.15 Histogram số ngày xem phim theo giới tính

ggplot(dataset, aes(x = days_watched, fill = gender)) +
  geom_histogram(bins = 40, color = "black", alpha = 0.7) +
  geom_density(aes(y = ..count..), color = "red", size = 1) +
  geom_vline(aes(xintercept = mean(days_watched, na.rm = TRUE)), 
             color = "blue", linetype = "dashed") +
  facet_wrap(~gender) +
  xlim(0, 1000) +
  theme_minimal() +
  labs(title = "Phân phối số ngày xem phim theo giới tính",
       x = "Số ngày xem", y = "Tần suất")
## 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.
## Warning: Removed 60 rows containing non-finite outside the scale range
## (`stat_bin()`).
## Warning: Removed 60 rows containing non-finite outside the scale range
## (`stat_density()`).
## Warning: Removed 6 rows containing missing values or values outside the scale range
## (`geom_bar()`).

Giải thích:
- geom_histogram(bins = 40, color = “black”, alpha = 0.7): Vẽ biểu đồ tần suất (histogram) thể hiện phân bố số ngày xem anime (days_watched) của người dùng. Biểu đồ được chia thành 40 khoảng (bins), mỗi cột thể hiện số lượng người dùng có tổng số ngày xem nằm trong một phạm vi nhất định. Các cột có viền đen và độ trong suốt alpha = 0.7 giúp dễ quan sát khi dữ liệu được tô màu khác nhau theo giới tính.
- geom_density(aes(y = ..count..), color = “red”, size = 1): Thêm đường mật độ màu đỏ để làm mượt phân bố dữ liệu, giúp quan sát xu hướng tổng thể thay vì chỉ nhìn qua các cột rời rạc. Đường này có cùng đơn vị với biểu đồ tần suất (do dùng ..count..), nên dễ dàng so sánh mật độ người xem ở các mức days_watched khác nhau.
- geom_vline(aes(xintercept = mean(days_watched, na.rm = TRUE)), color = “blue”, linetype = “dashed”): Thêm đường thẳng đứng màu xanh lam, nét đứt (dashed line) để biểu diễn giá trị trung bình của biến days_watched. Đường này giúp người đọc xác định vị trí trung bình so với phần lớn dữ liệu và quan sát xem phân bố có bị lệch (skewed) về bên trái hay phải.
- facet_wrap(~gender): Chia biểu đồ thành hai phần riêng biệt tương ứng với từng giới tính (nam và nữ). Cách hiển thị này giúp dễ dàng so sánh trực quan hành vi xem anime giữa hai giới, đặc biệt là về độ tập trung, mức trung bình và độ lệch phân bố.
- xlim(0, 1000): Giới hạn trục X từ 0 đến 1000 để tập trung hiển thị vùng dữ liệu chính, tránh các giá trị ngoại lai (outliers) khiến biểu đồ bị kéo giãn và khó quan sát phần trung tâm.
- theme_minimal(): Sử dụng giao diện tối giản, loại bỏ các đường nền và yếu tố không cần thiết, giúp biểu đồ rõ ràng, nhấn mạnh dữ liệu chính.
- labs(title = “Phân phối số ngày xem phim theo giới tính”, x = “Số ngày xem”, y = “Tần suất”): Đặt tiêu đề và nhãn trục, mô tả rõ nội dung biểu đồ là sự phân bố số ngày xem anime giữa các nhóm giới tính.
Nhận xét: Biểu đồ minh họa phân bố số ngày xem phim (days_watched) theo từng giới tính. Kết quả cho thấy các nhóm giới tính có phân bố tương đối tương đồng, tuy nhiên vẫn có sự khác biệt nhỏ: nhóm nam giới thường có số ngày xem cao hơn một chút, trong khi nhóm nữ giới có phân bố nghiêng về phía các giá trị thấp hơn. Đường trung bình màu đỏ cho thấy vị trí trung bình của mỗi nhóm, giúp dễ dàng nhận thấy sự chênh lệch nhẹ giữa các giới tính. Biểu đồ này gợi ý rằng giới tính có thể là một yếu tố ảnh hưởng đến thói quen xem anime, mặc dù mức độ khác biệt không quá lớn.

4.16 Histogram số lần xem lại anime (rewatched)

ggplot(dataset, aes(x = rewatched)) +
  geom_histogram(binwidth = 1, fill = "lightpink", color = "black", alpha = 0.7) +
  geom_vline(aes(xintercept = mean(rewatched, na.rm = TRUE)), 
             color = "darkred", linetype = "dotted") + 
  geom_density(aes(y = ..count.. * 2), color = "darkred") +                     
  labs(title = "Phân bố số lần xem lại anime", 
       x = "Số lần xem lại", y = "Tần suất") + 
  xlim(0, 100) +
  theme_bw()
## Warning: Removed 4576 rows containing non-finite outside the scale range
## (`stat_bin()`).
## Warning: Removed 4576 rows containing non-finite outside the scale range
## (`stat_density()`).
## Warning: Removed 2 rows containing missing values or values outside the scale range
## (`geom_bar()`).

Giải thích:
- geom_histogram(binwidth = 1, fill = “lightpink”, color = “black”, alpha = 0.7): Vẽ biểu đồ tần suất (histogram) thể hiện phân bố của biến rewatched – số lần người dùng xem lại anime. Mỗi cột đại diện cho số lượng người dùng có cùng (hoặc gần giống) số lần xem lại. Màu nền hồng nhạt (lightpink) giúp biểu đồ nhẹ mắt, đường viền đen làm nổi bật các cột, và độ trong suốt alpha = 0.7 tạo sự cân bằng giữa độ đậm và rõ ràng. Việc chọn binwidth = 1 cho phép hiển thị chi tiết số lần xem lại theo từng đơn vị, phù hợp với dữ liệu rời rạc.
- geom_vline(aes(xintercept = mean(rewatched, na.rm = TRUE)), color = “darkred”, linetype = “dotted”): Thêm một đường thẳng đứng biểu thị giá trị trung bình của số lần xem lại anime. Đường màu đỏ đậm và kiểu nét chấm (dotted) giúp dễ dàng nhận biết vị trí của trung bình – từ đó so sánh xem phần lớn người dùng xem lại ít hay nhiều hơn mức trung bình.
- geom_density(aes(y = ..count.. * 2), color = “darkred”): Chồng thêm đường mật độ (density curve) màu đỏ đậm để biểu diễn dạng phân bố mượt mà của dữ liệu. Đường này giúp hình dung xu hướng chung của tần suất xem lại anime, trong khi hệ số nhân (×2) giúp đường tỷ lệ hợp lý với cột histogram, đảm bảo tính trực quan khi so sánh.
- labs(title = “Phân bố số lần xem lại anime”, x = “Số lần xem lại”, y = “Tần suất”): Đặt tiêu đề và nhãn cho trục hoành và trục tung. Biểu đồ thể hiện rõ mục đích là mô tả phân bố số lần người dùng xem lại anime, giúp người đọc hiểu được hành vi tái xem trong cộng đồng người dùng.
- xlim(0, 100): Giới hạn trục X từ 0 đến 100 để loại bỏ các giá trị cực lớn hiếm gặp, giúp biểu đồ tập trung và mịn hơn ở vùng giá trị chính, nơi phần lớn người dùng tập trung.
- theme_bw(): Sử dụng giao diện nền trắng (black & white) mang tính học thuật, với đường trục rõ ràng, giúp biểu đồ chuyên nghiệp và dễ đọc hơn.
Nhận xét: Biểu đồ thể hiện tần suất số lần xem lại anime của người dùng. Dễ thấy rằng phần lớn người xem chỉ xem lại ít anime, trong khi chỉ có một nhóm nhỏ xem lại nhiều lần. Đường trung bình và đường mật độ thể hiện rõ xu hướng nghiêng phải (right-skewed), nghĩa là có một vài người dùng có xu hướng xem lại anime rất nhiều lần. Hiện tượng này phản ánh sự yêu thích mạnh mẽ của một nhóm nhỏ người xem trung thành, đồng thời cho thấy rằng hành vi xem lại không phổ biến rộng rãi trong cộng đồng nói chung.

4.17 Bar chart: Tình trạng phim đang xem

watch_status <- dataset %>%
  summarise(
    watching = sum(watching, na.rm=TRUE),
    completed = sum(completed, na.rm=TRUE),
    on_hold = sum(on_hold, na.rm=TRUE),
    dropped = sum(dropped, na.rm=TRUE),
    plan_to_watch = sum(plan_to_watch, na.rm=TRUE)
  ) %>% tidyr::pivot_longer(everything(), names_to="status", values_to="count")

ggplot(watch_status, aes(x=reorder(status, -count), y=count, fill=status)) +
  geom_col() +
  geom_text(aes(label=comma(count)), vjust=-0.5, size=3.5) +
  theme_bw() +
  labs(title="Tổng số lượng anime theo trạng thái", x="Trạng thái", y="Số lượng")

Giải thích:
- summarise(…): Lệnh này tính tổng số lượng anime của từng trạng thái xem trong bộ dữ liệu. Cụ thể:
+ watching: tổng số anime mà người dùng hiện đang xem.
+ completed: tổng số anime đã hoàn thành.
+ on_hold: tổng số anime đang tạm hoãn.
+ dropped: tổng số anime bị bỏ dở.
+ plan_to_watch: tổng số anime người dùng dự định xem trong tương lai. Việc dùng na.rm = TRUE giúp loại bỏ giá trị thiếu (NA), đảm bảo kết quả chính xác.
- pivot_longer(everything(), names_to = “status”, values_to = “count”): Chuyển bảng dữ liệu từ dạng rộng sang dài, tức là gom các cột trạng thái thành hai cột mới:
+ status: chứa tên trạng thái (watching, completed, on_hold, dropped, plan_to_watch).
+ count: chứa tổng số lượng tương ứng của mỗi trạng thái. Cấu trúc này thuận tiện cho việc trực quan hóa bằng ggplot2.
- ggplot(watch_status, aes(x = reorder(status, -count), y = count, fill = status)): Khởi tạo biểu đồ cột, trong đó:
+ X: tên trạng thái xem anime.
+ Y: tổng số lượng anime ở mỗi trạng thái.
+ fill = status: tô màu từng cột theo nhóm trạng thái khác nhau, giúp dễ phân biệt.
Hàm reorder(status, -count) sắp xếp các cột theo thứ tự giảm dần của giá trị count, từ cao nhất đến thấp nhất.
- geom_col(): Vẽ biểu đồ cột (column chart), trong đó chiều cao của mỗi cột thể hiện tổng số lượng anime theo từng trạng thái. Đây là dạng biểu đồ phù hợp để so sánh tổng thể giữa các nhóm phân loại.
- geom_text(aes(label = comma(count)), vjust = -0.5, size = 3.5): Thêm nhãn hiển thị giá trị thực tế trên đầu mỗi cột.
+ Hàm comma(count) (thuộc thư viện scales) giúp định dạng số có dấu phẩy ngăn cách hàng nghìn (ví dụ: 12,345), dễ đọc hơn.
+ Tham số vjust = -0.5 điều chỉnh vị trí nhãn nằm ngay trên cột để dễ nhìn.
- theme_bw(): Áp dụng giao diện nền trắng – khung đen (black-white theme), giúp biểu đồ rõ ràng, chuyên nghiệp, phù hợp cho báo cáo học thuật.
- labs(title = “Tổng số lượng anime theo trạng thái”, x = “Trạng thái”, y = “Số lượng”): Đặt tiêu đề và nhãn trục, mô tả rõ ý nghĩa biểu đồ — thể hiện tổng số anime mà người dùng đã hoàn thành, đang xem, tạm dừng, bỏ dở hoặc có kế hoạch xem.
Nhận xét: Biểu đồ cột so sánh số lượng anime theo từng trạng thái xem bao gồm: “Watching”, “Completed”, “On-Hold”, “Dropped” và “Plan to Watch”. Kết quả cho thấy số anime ở trạng thái “Completed” và “Plan to Watch” chiếm tỷ trọng cao nhất, chứng tỏ phần lớn người dùng hoặc đã hoàn thành xem, hoặc đang lên kế hoạch xem thêm nhiều anime mới. Ngược lại, hai trạng thái “Dropped” và “On-Hold” chiếm tỷ lệ thấp, phản ánh rằng người xem anime thường có xu hướng kiên trì thay vì bỏ dở giữa chừng. Biểu đồ này cho thấy cộng đồng người xem anime duy trì mức độ tương tác và hứng thú cao, thể hiện một văn hóa xem kiên định và liên tục mở rộng danh sách phim.

4.18 Line chart: Số lượng phim theo trạng thái (minh họa xu hướng)

ggplot(watch_status, aes(x=status, y=count, group=1)) +
  geom_line(color="darkblue", size=1) +
  geom_point(size=3, color="red") +
  geom_text(aes(label=count), vjust=-0.5) +
  theme_classic() +
  labs(title="Xu hướng số lượng phim theo trạng thái", x="Trạng thái", y="Số lượng")

Giải thích:
- ggplot(watch_status, aes(x = status, y = count, group = 1)): Khởi tạo biểu đồ đường (line chart) với dữ liệu từ bảng watch_status.
+ status: biểu diễn trên trục X, là các trạng thái xem anime (watching, completed, on_hold, dropped, plan_to_watch).
+ count: biểu diễn trên trục Y, là tổng số lượng anime tương ứng với từng trạng thái.
+ group = 1: đảm bảo các điểm dữ liệu được nối liền thành một đường duy nhất (thay vì nhiều nhóm tách biệt).
- geom_line(color = “darkblue”, size = 1): Vẽ đường nối giữa các điểm dữ liệu, thể hiện xu hướng thay đổi số lượng anime theo từng trạng thái.
+ Đường màu xanh đậm (darkblue) giúp nổi bật, thể hiện rõ mạch xu hướng.
+ size = 1 quy định độ dày của đường, tăng độ dễ nhìn trong báo cáo.
- geom_point(size = 3, color = “red”): Thêm các điểm dữ liệu màu đỏ tại mỗi vị trí trạng thái để làm nổi bật giá trị cụ thể. Các điểm này giúp người xem dễ dàng nhận biết và phân biệt từng giá trị hơn là chỉ nhìn đường xu hướng.
- geom_text(aes(label = count), vjust = -0.5): Thêm nhãn số lượng cụ thể ngay trên mỗi điểm dữ liệu.
vjust = -0.5 giúp các con số hiển thị phía trên điểm đỏ, tránh bị chồng lấn. Nhờ đó, người xem có thể đọc trực tiếp giá trị mà không cần ước lượng từ trục Y.
- theme_classic(): Áp dụng giao diện nền trắng và đường trục rõ nét, tạo cảm giác gọn gàng, chuyên nghiệp, phù hợp cho báo cáo thống kê hoặc học thuật.
- labs(title = “Xu hướng số lượng phim theo trạng thái”, x = “Trạng thái”, y = “Số lượng”): Đặt tiêu đề và nhãn cho hai trục, giúp người đọc hiểu rằng biểu đồ thể hiện sự biến động của tổng số anime giữa các trạng thái khác nhau trong quá trình xem.
Nhận xét: Biểu đồ cho thấy sự khác biệt rõ rệt giữa các trạng thái xem phim. Số lượng phim “đã xem” thường cao nhất, trong khi nhóm “đang xem” và “dự định xem” có xu hướng thấp hơn. Điều này phản ánh thói quen xem phim của người dùng có xu hướng hoàn thành danh sách phim đã chọn.

4.19 Histogram log-scale số tập phim

ggplot(dataset, aes(x = episodes_watched)) +
  geom_histogram(bins=50, fill="skyblue", color="black") +
  scale_x_log10() +
  geom_vline(aes(xintercept=median(episodes_watched,na.rm=TRUE)), color="red") +
  theme_bw() +
  labs(title="Phân phối log-scale số tập phim", x="Log(Số tập)", y="Tần suất")
## Warning in scale_x_log10(): log-10 transformation introduced infinite values.
## Warning: Removed 25766 rows containing non-finite outside the scale range
## (`stat_bin()`).

Giải thích:
- ggplot(dataset, aes(x = episodes_watched)): Khởi tạo biểu đồ phân bố (histogram) với dữ liệu từ dataset, trong đó biến episodes_watched (số tập phim mà người dùng đã xem) được chọn làm trục hoành. Mục tiêu là xem xét mức độ phân tán và tần suất của số tập phim giữa các người dùng.
- geom_histogram(bins = 50, fill = “skyblue”, color = “black”): Vẽ biểu đồ tần suất gồm 50 cột, mỗi cột đại diện cho một khoảng giá trị số tập phim.
+ bins = 50 giúp chia nhỏ dữ liệu thành 50 khoảng, thể hiện chi tiết hơn sự phân bố.
+ fill = “skyblue” tạo màu xanh nhạt nhẹ nhàng cho các cột, giúp dễ quan sát.
+ color = “black” thêm đường viền đen cho từng cột để phân tách rõ ràng giữa các nhóm dữ liệu.
- scale_x_log10(): Chuyển trục X sang thang đo logarit (log-scale). Đây là một bước rất quan trọng khi dữ liệu bị lệch mạnh về một phía — chẳng hạn có nhiều người xem rất ít tập, trong khi một số ít người xem hàng ngàn tập. Việc dùng log giúp thu hẹp khoảng cách giữa các giá trị lớn và nhỏ, làm cho biểu đồ cân đối và dễ đọc hơn, đồng thời thể hiện rõ hơn xu hướng tổng thể của dữ liệu.
- geom_vline(aes(xintercept = median(episodes_watched, na.rm = TRUE)), color = “red”): Thêm đường thẳng đứng màu đỏ tại vị trí trung vị (median) của số tập phim đã xem. Việc dùng trung vị thay vì trung bình giúp biểu diễn giá trị “đại diện” cho phần lớn người dùng, tránh bị ảnh hưởng bởi các giá trị ngoại lai (outliers) — chẳng hạn vài người xem quá nhiều anime.
- theme_bw(): Áp dụng chủ đề nền trắng (black-white theme), giúp các chi tiết như cột màu xanh và đường trung vị đỏ nổi bật rõ ràng, phù hợp cho các báo cáo học thuật.
- labs(title = “Phân phối log-scale số tập phim”, x = “Log(Số tập)”, y = “Tần suất”): Đặt tiêu đề và nhãn trục cho biểu đồ, làm rõ rằng biểu đồ này thể hiện phân bố của số tập phim đã xem trên thang logarit.
Nhận xét: Biểu đồ cho thấy phần lớn người dùng chỉ xem một lượng nhỏ đến trung bình số tập phim, thể hiện qua mật độ tập trung ở phía bên trái (tức giá trị log nhỏ). Khi chuyển sang thang log, ta dễ dàng nhận ra xu hướng giảm dần của tần suất khi số tập phim tăng. Điều này phản ánh thực tế rằng chỉ có một nhóm nhỏ người dùng xem số lượng anime rất lớn, trong khi phần đông dừng lại ở mức vừa phải.

4.20 Bar chart: Trung bình mean_score theo giới tính

dataset %>%
  group_by(gender) %>%
  summarise(mean_score_avg = mean(mean_score, na.rm=TRUE)) %>%
  ggplot(aes(x=gender, y=mean_score_avg, fill=gender)) +
  geom_col() +
  geom_text(aes(label=round(mean_score_avg,2)), vjust=-0.5) +
  theme_minimal() +
  labs(title="Điểm trung bình theo giới tính", x="Giới tính", y="Điểm trung bình")

Giải thích:
- group_by(gender):chia bộ dữ liệu thành các nhóm theo biến gender (giới tính nam, nữ, hoặc khác nếu có).
- summarise(mean_score_avg = mean(mean_score, na.rm=TRUE))
+ Hàm mean() tính giá trị trung bình của biến mean_score.
+ Tham số na.rm=TRUE giúp loại bỏ các giá trị bị thiếu (NA) trong quá trình tính toán. Kết quả trả về là một bảng mới gồm hai cột: gender và mean_score_avg
- geom_col(): Dùng để vẽ cột với chiều cao tương ứng giá trị trung bình của mỗi nhóm giới tính. Màu sắc của từng cột được xác định bởi biến fill = gender.
- geom_text(aes(label=round(mean_score_avg,2)), vjust=-0.5)
+ label hiển thị giá trị trung bình được làm tròn đến 2 chữ số thập phân.
+ vjust = -0.5 điều chỉnh vị trí nhãn nằm phía trên đầu cột, giúp dễ nhìn.
- theme_minimal(): Sử dụng giao diện tối giản giúp biểu đồ gọn gàng, dễ đọc.
- labs(title=“Điểm trung bình theo giới tính”, x=“Giới tính”, y=“Điểm trung bình”)
Nhận xét: Điểm trung bình đánh giá phim giữa các giới tính có sự chênh lệch nhẹ. Nhóm nữ thường cho điểm cao hơn một chút so với nhóm nam, phản ánh xu hướng đánh giá tích cực hơn của người dùng nữ.

4.21 Boxplot điểm trung bình theo giới tính

ggplot(dataset, aes(x = gender, y = mean_score, fill = gender)) +
  geom_boxplot(alpha = 0.7) +
  geom_jitter(width = 0.2, alpha = 0.2, color = "black") +
  stat_summary(fun=mean, geom="point", shape=18, color="red", size=3) +
  theme_light() +
  labs(title="Phân phối điểm trung bình theo giới tính", x="Giới tính", y="Điểm trung bình")

Giải thích:
- ggplot() với dữ liệu đầu vào là dataset, trong đó trục hoành biểu diễn biến giới tính (gender) và trục tung biểu diễn điểm trung bình (mean_score). Các hộp trong biểu đồ được tô màu theo giới tính nhờ tham số fill = gender, giúp dễ dàng phân biệt giữa các nhóm.
- geom_boxplot(alpha = 0.7) được sử dụng để vẽ biểu đồ hộp (boxplot), thể hiện phân bố điểm trung bình của từng nhóm giới tính với độ trong suốt vừa phải để không che mất các lớp khác.
- geom_jitter(width = 0.2, alpha = 0.2, color = “black”) được thêm vào để hiển thị các điểm dữ liệu riêng lẻ, cho phép quan sát rõ hơn mức độ phân tán của từng cá nhân trong mỗi nhóm giới tính, đồng thời tránh việc các điểm bị chồng lên nhau.
- stat_summary(fun = mean, geom = “point”, shape = 18, color = “red”, size = 3) được dùng để đánh dấu giá trị trung bình của từng nhóm bằng một điểm màu đỏ hình thoi, giúp người xem dễ dàng nhận biết xu hướng trung bình của mỗi giới.
- theme_light(): Giao diện sáng, nhẹ nhàng, rõ ràng và dễ đọc.
- labs(), với tiêu đề “Phân phối điểm trung bình theo giới tính” cùng các nhãn trục “Giới tính” và “Điểm trung bình”, giúp biểu đồ có tính mô tả tốt hơn.
Nhận xét: Biểu đồ thể hiện sự khác biệt về điểm trung bình giữa các giới tính. Kết quả cho thấy điểm trung bình của các nhóm không có sự chênh lệch quá lớn, tuy nhiên vẫn tồn tại những khác biệt nhỏ về giá trị trung vị và mức độ phân tán. Nhóm nam nhìn chung có điểm trung bình cao hơn một chút so với nhóm nữ và nhóm phi nhị nguyên giới, cho thấy có thể tồn tại sự khác biệt trong xu hướng hoặc tiêu chí đánh giá phim giữa các giới tính.

4.22 Biểu đồ phân phối số tập phim đã xem

dataset_filtered <- dataset %>% filter(episodes_watched < 10000)
ggplot(dataset_filtered, aes(x = episodes_watched, fill = gender)) +
  geom_histogram(bins = 50, color = "black", alpha = 0.6) +        
  geom_density(aes(y = ..count..), color = "red", size = 1) +      
  geom_vline(aes(xintercept = mean(episodes_watched, na.rm = TRUE)), 
             color = "blue", linetype = "dashed", size = 1) +      
  facet_wrap(~gender) +                                            
  theme_minimal() +                                                
  labs(title = "Phân phối số tập phim đã xem (loại bỏ outlier)",
       x = "Số tập phim đã xem", 
       y = "Tần suất") +

  scale_x_continuous(labels = scales::comma)

Giải thích:
- dataset_filtered <- dataset %>% filter(episodes_watched < 10000): giúp giữ lại những quan sát có số tập phim nhỏ hơn 10.000, đảm bảo dữ liệu không bị ảnh hưởng bởi các trường hợp quá lớn, từ đó phản ánh trung thực hơn thói quen xem phim của người dùng.
- hàm ggplot() được sử dụng với biến episodes_watched làm trục hoành, thể hiện số tập phim đã xem, và fill = gender để tô màu khác nhau cho từng giới tính.
- geom_histogram(bins = 50, color = “black”, alpha = 0.6) vẽ biểu đồ cột thể hiện tần suất xem phim, trong đó dữ liệu được chia thành 50 khoảng (bins), giúp biểu đồ mượt mà hơn. Mỗi cột có viền đen (color = “black”) và độ trong suốt 60% (alpha = 0.6) để các cột không bị che lấp khi chồng màu. Tiếp theo, geom_density(aes(y = ..count..), color = “red”, size = 1) thêm đường mật độ phân phối (density curve) màu đỏ phủ lên biểu đồ cột, cho phép quan sát xu hướng chung của dữ liệu dưới dạng đường cong liên tục thay vì chỉ qua các cột rời rạc.
- geom_vline(aes(xintercept = mean(episodes_watched, na.rm = TRUE)), color = “blue”, linetype = “dashed”, size = 1) vẽ một đường thẳng đứng màu xanh tại vị trí giá trị trung bình của số tập phim đã xem, giúp người xem dễ dàng nhận biết mức trung bình so với toàn bộ phân phối.
- facet_wrap(~gender), cho phép so sánh trực tiếp phân phối giữa các nhóm nam và nữ trên cùng một thang đo. Giao diện được làm gọn gàng và hiện đại nhờ chủ đề theme_minimal().
- labs() để biểu đồ có tiêu đề “Phân phối số tập phim đã xem (loại bỏ outlier)” cùng các trục được đặt tên rõ ràng (“Số tập phim đã xem” và “Tần suất”).
- scale_x_continuous(labels = scales::comma) giúp hiển thị giá trị trục hoành ở dạng có dấu phẩy (ví dụ: 1,000; 5,000) nhằm tăng tính trực quan và dễ đọc.
Nhận xét: Biểu đồ mô tả phân phối số tập phim đã xem (sau khi loại bỏ các giá trị ngoại lai) của người dùng theo giới tính. Kết quả cho thấy phân phối của cả ba nhóm đều lệch phải, nghĩa là phần lớn người dùng chỉ xem một số lượng nhỏ tập phim, trong khi chỉ một số ít xem rất nhiều. Trung bình số tập phim đã xem của nhóm nam cao hơn so với nhóm nữ và nhóm phi nhị nguyên giới, điều này gợi ý rằng nhóm nam có xu hướng dành nhiều thời gian cho việc xem phim hơn. Sự khác biệt về giá trị trung bình giữa các nhóm giới tính phản ánh mức độ quan tâm và tần suất xem phim khác nhau, cho thấy yếu tố giới tính có thể ảnh hưởng đến hành vi tiêu thụ nội dung giải trí.