Chương 1: Phân tích Bộ dữ liệu Lực lượng lao động Hoa Kỳ (2023)

Nội dung của chương này tập trung vào việc thực hiện Khám phá dữ liệu sơ bộ (Exploratory Data Analysis - EDA) trên bộ dữ liệu về Lực lượng Lao động Hoa Kỳ năm 2023. Các thao tác được thực hiện một cách tuần tự, từ việc tải và chuẩn bị dữ liệu, kiểm tra cấu trúc và chất lượng, cho đến các bước xử lý và mã hóa biến. Mục tiêu là xây dựng một nền tảng hiểu biết vững chắc về dữ liệu trước khi tiến hành các phân tích thống kê chuyên sâu và trực quan hóa..

1. Thông tin tổng quan về bộ dữ liệu

1.1. Đọc và chuẩn bị dữ liệu

# Đọc dữ liệu và làm sạch tên cột
df_raw <- read_excel("data_2023_hoaKy.xlsx")
df <- df_raw %>%
  clean_names()

# Xem 6 dòng đầu tiên
df %>%
  head() %>%
  kable(caption = "6 dòng dữ liệu đầu tiên sau khi làm sạch tên cột") %>%
  kable_styling(bootstrap_options = "striped", font_size = 9, full_width = FALSE)
6 dòng dữ liệu đầu tiên sau khi làm sạch tên cột
age sex race marst empstat occ ind educ fullpart incwage
66 Female White Separated Not in labor force Not in universe N/A (not applicable) Some college Not working 0
68 Female White Separated Not in labor force Not in universe N/A (not applicable) Some college Not working 0
52 Female White Married, spouse present Not in labor force Not in universe N/A (not applicable) Some college Not working 0
51 Male White Married, spouse present Employed Retail salespersons Auto parts, accessories, and tire stores Some college Full-time 42000
78 Female White Separated Not in labor force Not in universe N/A (not applicable) Some college Not working 0
65 Male White Married, spouse present Not in labor force Janitors and building cleaners Colleges and universities, including junior colleges Some college Full-time 55000
# Chuyển các biến định tính sang factor để có ý nghĩa thống kê
factor_vars <- c("sex", "race", "marst", "empst", "occ", "ind", "educ", "fullpart")

# Kiểm tra các cột có tồn tại trong dataset trước khi chuyển
factor_vars <- factor_vars[factor_vars %in% names(df)]

df <- df %>%
  mutate(across(where(is.character), as.factor))

# Kiểm tra lại kiểu dữ liệu sau khi chuyển
glimpse(df)
Rows: 146,133
Columns: 10
$ age      <dbl> 66, 68, 52, 51, 78, 65, 68, 74, 74, 76, 75, 63, 64, 41, 1, 52…
$ sex      <fct> Female, Female, Female, Male, Female, Male, Female, Female, M…
$ race     <fct> White, White, White, White, White, White, White, White, White…
$ marst    <fct> "Separated", "Separated", "Married, spouse present", "Married…
$ empstat  <fct> Not in labor force, Not in labor force, Not in labor force, E…
$ occ      <fct> "Not in universe", "Not in universe", "Not in universe", "Ret…
$ ind      <fct> "N/A (not applicable)", "N/A (not applicable)", "N/A (not app…
$ educ     <fct> Some college, Some college, Some college, Some college, Some …
$ fullpart <fct> Not working, Not working, Not working, Full-time, Not working…
$ incwage  <dbl> 0, 0, 0, 42000, 0, 55000, 52000, 0, 0, 0, 0, 22000, 45000, 0,…

Quá trình phân tích bắt đầu bằng việc nhập dữ liệu từ file data_2023_hoaKy.xlsx vào môi trường R bằng hàm read_excel(). Để đảm bảo tính nhất quán và tuân thủ quy tắc đặt tên biến trong R, hàm clean_names() từ gói janitor đã được áp dụng để chuẩn hóa tên các cột. Sáu quan sát đầu tiên được hiển thị thông qua hàm head() để có cái nhìn trực quan ban đầu về cấu trúc dữ liệu. Cuối cùng, để tối ưu hóa cho các phân tích thống kê, các biến có kiểu dữ liệu character đã được chuyển đổi sang kiểu factor bằng hàm mutate()across(). Thao tác này giúp R nhận diện chính xác các biến định tính, tạo điều kiện thuận lợi cho việc phân nhóm và trực quan hóa sau này.

1.2. Kiểm tra cấu trúc dữ liệu

str(df)
tibble [146,133 × 10] (S3: tbl_df/tbl/data.frame)
 $ age     : num [1:146133] 66 68 52 51 78 65 68 74 74 76 ...
 $ sex     : Factor w/ 2 levels "Female","Male": 1 1 1 2 1 2 1 1 2 2 ...
 $ race    : Factor w/ 6 levels "American Indian",..: 6 6 6 6 6 6 6 6 6 6 ...
 $ marst   : Factor w/ 6 levels "Divorced","Married, spouse absent",..: 4 4 3 3 4 3 3 3 3 3 ...
 $ empstat : Factor w/ 4 levels "Employed","NIU",..: 3 3 3 1 3 3 3 3 3 3 ...
 $ occ     : Factor w/ 527 levels "Accountants and auditors",..: 319 319 319 438 319 251 368 319 319 319 ...
 $ ind     : Factor w/ 264 levels "Accounting, tax preparation, bookkeeping and payroll services",..: 149 149 149 20 149 45 120 149 149 149 ...
 $ educ    : Factor w/ 5 levels "Associate degree",..: 4 4 4 4 4 4 1 5 5 3 ...
 $ fullpart: Factor w/ 3 levels "Full-time","Not working",..: 2 2 2 1 2 1 1 2 3 2 ...
 $ incwage : num [1:146133] 0 0 0 42000 0 55000 52000 0 0 0 ...

Hàm str() (structure) được sử dụng để kiểm tra cấu trúc chi tiết của dataframe. Kết quả cung cấp thông tin về kiểu dữ liệu của từng biến (fct cho factor, dbl cho số thực), xác nhận rằng các bước chuẩn bị dữ liệu ban đầu đã được thực hiện thành công và dữ liệu đã sẵn sàng cho các bước tiếp theo.

1.3. Kích thước bộ dữ liệu

cat(paste("Bộ dữ liệu có", nrow(df), "quan sát và", ncol(df), "biến.\n"))
Bộ dữ liệu có 146133 quan sát và 10 biến.

Kết quả từ hàm nrow()ncol() cho thấy bộ dữ liệu có quy mô lớn, bao gồm 146,133 quan sát10 biến. Kích thước mẫu lớn này cung cấp một nền tảng vững chắc, đảm bảo tính đại diện và độ tin cậy thống kê cho các kết luận được rút ra từ phân tích.

1.4. Kiểm tra tên biến và dữ liệu

1.4.1. Kiểm tra tên biến

names(df)
 [1] "age"      "sex"      "race"     "marst"    "empstat"  "occ"     
 [7] "ind"      "educ"     "fullpart" "incwage" 

1.4.2. Kiểm tra dữ liệu

head(df)

Việc kiểm tra lại tên biến bằng names() và dữ liệu mẫu bằng head() là một bước xác thực cuối cùng, đảm bảo rằng dữ liệu đã được tải vào một cách chính xác và các tên biến đã được chuẩn hóa đúng như mong đợi.

1.5. Kiểm tra dữ liệu bị thiếu

na_by_column <- colSums(is.na(df))
columns_with_na <- na_by_column[na_by_column > 0]

if(length(columns_with_na) > 0) {
  cat("Các cột có giá trị bị thiếu:\n")
  data.frame(
    Column = names(columns_with_na),
    NA_Count = columns_with_na
  ) %>%
    kable(caption = "Số lượng giá trị NA theo cột") %>%
    kable_styling()
} else {
  cat("\nBộ dữ liệu không có giá trị bị thiếu nào.")
}

Bộ dữ liệu không có giá trị bị thiếu nào.

Chất lượng dữ liệu là một yếu tố quan trọng ảnh hưởng đến độ tin cậy của phân tích. Kết quả từ việc sử dụng hàm colSums(is.na(df)) cho thấy bộ dữ liệu không chứa bất kỳ giá trị bị thiếu (NA) nào. Điều này là một tín hiệu rất tích cực, cho phép chúng ta tiến hành phân tích mà không cần áp dụng các kỹ thuật xử lý dữ liệu thiếu phức tạp.

1.6. Kiểm tra dữ liệu trùng lặp

1.6.1. Kiểm tra tổng số dòng bị trùng hoàn toàn

num_duplicated_rows <- sum(duplicated(df))
cat(paste("Tổng số dòng bị trùng lặp hoàn toàn là:", num_duplicated_rows, "\n"))
Tổng số dòng bị trùng lặp hoàn toàn là: 65820 

1.6.2. Thống kê tần suất xuất hiện của từng dòng (tính theo toàn bộ các biến)

# Thống kê tần suất xuất hiện của từng dòng (xét toàn bộ biến)
duplicated_summary <- df %>%
  group_by(across(everything())) %>%
  summarise(Frequency = n(), .groups = "drop") %>%
  arrange(desc(Frequency))

# Hiển thị 10 dòng đầu tiên trong bảng tần suất
duplicated_summary %>%
  sample_n(10) %>%
  kable(caption = "10 dòng ngẫu nhiên trong bảng tần suất trùng lặp toàn bộ biến") %>%
  kable_styling(bootstrap_options = "striped", font_size = 9, full_width = FALSE)
10 dòng ngẫu nhiên trong bảng tần suất trùng lặp toàn bộ biến
age sex race marst empstat occ ind educ fullpart incwage Frequency
44 Female White Married, spouse present Employed Registered nurses Administration of human resource programs Graduate degree Full-time 139000 1
48 Male White Married, spouse present Employed Producers and directors Lessors of real estate, and offices of real estate agents and brokers Graduate degree Full-time 75000 1
49 Female White Single Employed Medical assistants General medical and surgical hospitals, and specialty (except psychiatric and substance abuse) hospitals Some college Full-time 52000 1
48 Female White Married, spouse present Employed Janitors and building cleaners Services to buildings and dwellings (except cleaning during construction and immediately after construction) Some college Full-time 13500 1
65 Male White Married, spouse present Employed Managers, all other Automotive repair and maintenance Associate degree Full-time 0 1
31 Male Asian Single Employed Automotive service technicians and mechanics Automotive repair and maintenance Some college Full-time 35000 1
54 Male White Married, spouse present Not in labor force Not in universe N/A (not applicable) Graduate degree Not working 0 5
55 Female American Indian Divorced Not in labor force Not in universe N/A (not applicable) Some high school Full-time 28000 1
50 Male White Divorced Employed Cooks Restaurants and other food services Some college Part-time 10000 1
30 Female White Widowed Not in labor force Not in universe N/A (not applicable) Some college Not working 0 2

1.6.3. Tần suất trùng lặp cao nhất và thấp nhất

max_freq <- max(duplicated_summary$Frequency)
min_freq <- min(duplicated_summary$Frequency)

cat(paste("Tần suất trùng lặp cao nhất là:", max_freq, "\n"))
Tần suất trùng lặp cao nhất là: 906 
cat(paste("Tần suất trùng lặp thấp nhất là:", min_freq, "\n"))
Tần suất trùng lặp thấp nhất là: 1 

1.6.4. Giải thích và kết luận

Phân tích cho thấy có 65,820 dòng dữ liệu bị trùng lặp hoàn toàn. Để hiểu sâu hơn, chúng tôi đã thống kê tần suất xuất hiện của từng bộ đặc điểm duy nhất. Kết quả cho thấy một số bộ đặc điểm có thể lặp lại tới 16 lần. Trong bối cảnh dữ liệu điều tra nhân khẩu học, hiện tượng này có thể phản ánh các cá nhân hoặc hộ gia đình có cùng một tập hợp đặc điểm (ví dụ: độ tuổi, giới tính, khu vực sinh sống, thu nhập,…). Do đó, nhóm quyết định giữ nguyên các dòng trùng lặp vì chúng được coi là một đặc tính của mẫu điều tra, không phải là lỗi nhập liệu..

1.7. Thống kê mô tả cơ bản

summary(df)
      age            sex                      race       
 Min.   : 0.00   Female:74834   American Indian :  2195  
 1st Qu.:18.00   Male  :71299   Asian           : 10757  
 Median :38.00                  Black           : 17187  
 Mean   :38.73                  Multiracial     :   806  
 3rd Qu.:58.00                  Pacific Islander:  3567  
 Max.   :85.00                  White           :111621  
                                                         
                     marst                     empstat     
 Divorced               :11085   Employed          :69160  
 Married, spouse absent : 1642   NIU               :29483  
 Married, spouse present:58636   Not in labor force:45018  
 Separated              : 6564   Unemployed        : 2472  
 Single                 :66223                             
 Widowed                : 1983                             
                                                           
                                             occ       
 Not in universe                               :74481  
 Managers, all other                           : 2510  
 Elementary and middle school teachers         : 1638  
 Driver/sales workers and truck drivers        : 1619  
 Registered nurses                             : 1498  
 First-Line supervisors of retail sales workers: 1324  
 (Other)                                       :63063  
                                                                                                       ind       
 N/A (not applicable)                                                                                    :74481  
 Construction                                                                                            : 5368  
 Elementary and secondary schools                                                                        : 4493  
 Restaurants and other food services                                                                     : 4264  
 General medical and surgical hospitals, and specialty (except psychiatric and substance abuse) hospitals: 3349  
 Colleges and universities, including junior colleges                                                    : 1718  
 (Other)                                                                                                 :52460  
                educ              fullpart        incwage       
 Associate degree :11033   Full-time  :59851   Min.   :      0  
 Elementary school:29813   Not working:72947   1st Qu.:      0  
 Graduate degree  :38150   Part-time  :13335   Median :      0  
 Some college     :51903                       Mean   :  31456  
 Some high school :15234                       3rd Qu.:  45000  
                                               Max.   :1549999  
                                                                

Hàm summary() cung cấp một cái nhìn tổng quan về phân phối của từng biến. Đối với các biến định lượng như ageincwage, nó cung cấp các giá trị min, max, trung bình, trung vị và các tứ phân vị. Đối với các biến định tính, nó liệt kê tần số của các cấp độ phổ biến nhất. Kết quả sơ bộ cho thấy incwage có độ lệch rất lớn (giá trị trung bình cao hơn nhiều so với trung vị), một đặc điểm cần được lưu ý trong các phân tích sau.

1.8. Trực quan hóa sơ bộ bằng Histogram

1.8.1. Tổng quan cấu trúc (số biến số, biến chữ, giá trị thiếu,…)

plot_intro(df, title = "Tổng quan cấu trúc dữ liệu")

1.8.2. Tỷ lệ giá trị thiếu (nếu có)

plot_missing(df, title = "Tỷ lệ giá trị thiếu của từng biến")

1.8.3. Phân bố các biến định tính (categorical variables)

update_geom_defaults("bar", list(orientation = "y"))
plot_bar(df, title = "Phân bố tần suất của các biến định tính")

1.8.4. Phân bố các biến định lượng (numeric variables)

plot_histogram(df, title = "Phân bố của các biến định lượng")

Để có một cái nhìn trực quan và nhanh chóng về dữ liệu, chúng tôi đã sử dụng các hàm từ gói DataExplorer. plot_intro() cung cấp một “báo cáo sức khỏe” tổng thể. plot_missing() xác nhận lại việc không có dữ liệu thiếu. plot_bar() cho thấy sự phân bổ của các biến định tính, trong khi plot_histogram() minh họa phân phối của các biến định lượng. Đáng chú ý, biểu đồ histogram của incwage một lần nữa xác nhận sự phân phối lệch phải mạnh, củng cố sự cần thiết của các phép biến đổi dữ liệu.

2. Xử lý dữ liệu thô và mã hóa biến

Dựa trên giả định nghiên cứu về mối quan hệ giữa học vấn, giới tính, tuổi tác và thu nhập, phần này thực hiện các thao tác xử lý và mã hóa nhằm chuẩn bị dữ liệu cho các phân tích thống kê và mô hình hóa.

2.3. Loại bỏ giá trị thu nhập bất thường

df <- df %>%
  mutate(incwage = as.numeric(incwage)) %>%
  filter(!is.na(incwage) & incwage >= 0 & incwage < 500000)

2.4. Phân nhóm thu nhập (phân tổ theo ngưỡng)

df <- df %>%
  mutate(income_group = case_when(
    incwage < 25000 ~ "Thấp",
    incwage >= 25000 & incwage < 50000 ~ "Trung bình thấp",
    incwage >= 50000 & incwage < 100000 ~ "Trung bình cao",
    incwage >= 100000 ~ "Cao",
    TRUE ~ "Không xác định"
  ))

df$income_group <- factor(df$income_group, 
                          levels = c("Thấp", "Trung bình thấp", "Trung bình cao", "Cao"))

df %>%
  count(income_group, name = "Số lượng") %>%
  kable(caption = "Tần số các nhóm thu nhập sau khi phân loại") %>% # ✅ Hiển thị tần số nhóm thu nhập
  kable_styling(full_width = FALSE)
Tần số các nhóm thu nhập sau khi phân loại
income_group Số lượng
Thấp 92646
Trung bình thấp 19105
Trung bình cao 22165
Cao 11757

2.5. Chuẩn hoá tình trạng việc làm

df <- df %>%
  mutate(empstat = str_squish(as.character(empstat)),
         emp_status = case_when(
           empstat %in% c("Employed") ~ "Có việc làm",
           empstat %in% c("Unemployed") ~ "Thất nghiệp",
           empstat %in% c("Not in labor force") ~ "Ngoài lực lượng lao động",
           TRUE ~ "Không xác định"
         ))

df$emp_status <- factor(df$emp_status, 
                        levels = c("Có việc làm", "Thất nghiệp", 
                                   "Ngoài lực lượng lao động", "Không xác định"))

df %>%
  count(emp_status, name = "Số lượng") %>%
  kable(caption = "Tần số theo tình trạng việc làm") %>% #Hiển thị kết quả kiểm tra sau chuẩn hoá
  kable_styling(full_width = FALSE) 
Tần số theo tình trạng việc làm
emp_status Số lượng
Có việc làm 68710
Thất nghiệp 2470
Ngoài lực lượng lao động 45010
Không xác định 29483

2.6. Chuẩn hoá trình độ học vấn

df <- df %>%
  mutate(educ_group = case_when(
    educ %in% c("Less than high school", "Elementary school") ~ "Thấp",
    educ %in% c("High school graduate", "Some college") ~ "Trung bình",
    educ %in% c("Associate degree", "Bachelor's degree", "Graduate degree") ~ "Cao",
    TRUE ~ "Không xác định"
  ))

df$educ_group <- factor(df$educ_group, levels = c("Thấp", "Trung bình", "Cao"))

df %>%
  count(educ_group, name = "Số lượng") %>%
  kable(caption = "Tần số trình độ học vấn sau khi nhóm") %>% #Hiển thị kết quả kiểm tra
  kable_styling(full_width = FALSE)
Tần số trình độ học vấn sau khi nhóm
educ_group Số lượng
Thấp 29811
Trung bình 51822
Cao 48823
NA 15217

2.7. Tạo nhóm tuổi

df <- df %>%
  mutate(age = as.numeric(age),
         age_group = case_when(
           age < 25 ~ "Dưới 25",
           age >= 25 & age < 40 ~ "25–39",
           age >= 40 & age < 60 ~ "40–59",
           age >= 60 ~ "Từ 60 trở lên",
           TRUE ~ "Không xác định"
         ))

df$age_group <- factor(df$age_group, 
                       levels = c("Dưới 25", "25–39", "40–59", "Từ 60 trở lên"))

df %>%
  count(age_group, name = "Số lượng") %>%
  kable(caption = "Tần số các nhóm tuổi") %>% #In kết quả tần số nhóm tuổi
  kable_styling(full_width = FALSE)
Tần số các nhóm tuổi
age_group Số lượng
Dưới 25 47603
25–39 28110
40–59 36195
Từ 60 trở lên 33765

Quá trình xử lý dữ liệu bắt đầu bằng việc loại bỏ các giá trị thu nhập ngoại lai (trên 500,000 USD/năm) để tăng độ tin cậy của phân tích. Tiếp theo, các biến liên tục và biến định tính chi tiết đã được mã hóa thành các nhóm có ý nghĩa hơn: incwage được phân thành 4 nhóm thu nhập; empstat được gán nhãn tiếng Việt rõ ràng; educ được nhóm thành 3 cấp độ học vấn “Thấp-Trung bình-Cao”; và age được chia thành 4 nhóm tuổi theo các giai đoạn sự nghiệp. Việc sắp xếp thứ tự cho các biến factor mới tạo (income_group, emp_status, educ_group, age_group) đảm bảo rằng các biểu đồ và bảng phân tích sẽ được trình bày một cách logic. Cuối cùng, các kết quả tính toán trước như thu nhập trung bình theo giới tính, nhóm tuổi và tình trạng việc làm đã được tính toán và lưu vào các bảng riêng (table1, table2, table3) để tối ưu hóa việc sử dụng lại trong các phần sau.

3.1. Tính thu nhập trung bình theo giới tính

table1 <- df %>%
  group_by(sex) %>%
  summarise(mean_income = mean(incwage, na.rm = TRUE))

table1 %>%
  kable(caption = "Thu nhập trung bình theo giới tính") %>%
  kable_styling(full_width = FALSE)
Thu nhập trung bình theo giới tính
sex mean_income
Female 23127.62
Male 35154.58

3.2. Tính thu nhập trung bình theo nhóm tuổi

table2 <- df %>%
  group_by(age_group) %>%
  summarise(mean_income = mean(incwage, na.rm = TRUE))

table2 %>%
  kable(caption = "Thu nhập trung bình theo nhóm tuổi") %>%
  kable_styling(full_width = FALSE)
Thu nhập trung bình theo nhóm tuổi
age_group mean_income
Dưới 25 4175.402
25–39 49544.484
40–59 56737.670
Từ 60 trở lên 17109.501

3.3. Tính thu nhập trung bình theo tình trạng việc làm

table3 <- df %>%
  group_by(emp_status) %>%
  summarise(mean_income = mean(incwage, na.rm = TRUE))

table3 %>%
  kable(caption = "Thu nhập trung bình theo tình trạng việc làm") %>%
  kable_styling(full_width = FALSE)
Thu nhập trung bình theo tình trạng việc làm
emp_status mean_income
Có việc làm 59101.391
Thất nghiệp 24527.068
Ngoài lực lượng lao động 2251.522
Không xác định 0.000