20251208
離職抑制のためのデータ分析
日本では、人手不足が深刻な問題となっていて、企業では採用以上に離職抑制(リテンション)が大きな課題となっている。
どのような人が満足度高く働いていて、どのような人が離職してしまうのかをデータから検討してみよう。
Kggleのサンプルデータを用いて分析を行う。
https://gyazo.com/3e5a884eed6f68246daf53ed9d07d505
仕事の満足度、前回の評価、担当しているプロジェクト数、労働時間、勤続年数、労災の有無、離職、過去5年間の昇進有無、部署、賃金のデータがある。
知りたいのは、どんな人がなぜ離職するのか?
このデータは、ある会社を離職した人(left=1)と在籍している人(left=0)に注目して、どんな人がなぜ会社を退職してしまうのかを分析するためのもの。人手不足が深刻な日本企業でも、離職抑制は重要な課題。このデータを可視化してみよう。
まずはデータを読みこんで、内容を確認する。
デスクトップにあるHR_comma_sep01.csvというファイルをdfという名前でRに読み込む。
ヘッダーがあるのでTRUEに、また所属部署や賃金などの文字列を勝手にfactor化しない(FALSE)。
code:R
df <- read.csv("~/Desktop/HR_comma_sep01.csv",
header = TRUE,
stringsAsFactors = FALSE)
またワーキングディレクトリを今はdesktopにしておく。
code:R
setwd("~/Desktop")
仕事の満足度のヒストグラムを描いてみよう。
その際にRの初期設定のままのシンプルな作図ではなくggplotを使うと描きやすいし見やすい
code:R
library(ggplot2)
ggplot(df, aes(x = satisfaction_level)) +
geom_histogram(
bins = 30,
fill = "lightblue",
color = "white"
) +
labs(
title = "Distribution of Satisfaction Level",
x = "Satisfaction Level",
y = "Frequency"
) +
theme_minimal()
https://gyazo.com/24e91fcc38b9c70738ef4c3a6781d6eb
次に日本語を利用できるようにしておこう
code:R
# showtextとsysfontsをインストール(初回のみ)
install.packages(c("showtext", "sysfonts"))
library(showtext)
library(sysfonts)
# Google Fontsから日本語フォント(Noto Sans JP)をダウンロードし、Rに登録
# 'notosansjp'という名前で登録します
font_add_google("Noto Sans JP", "notosansjp")
# 登録したフォントを自動で使えるように設定
showtext_auto()
可視化の取り組み
課題:給与水準(salary: low/medium/high)が離職にどのように影響するかを比較し、給与水準ごとの離職者数を可視化してください
code:R
# 給与水準別の離職者・残留者数の比較
ggplot(df, aes(x = salary, fill = factor(left))) +
# 棒グラフを作成し、fillで離職の有無を色分け
geom_bar(position = "stack", color = "black") +
labs(title = "給与水準別の離職者数の比較",
x = "給与水準 (Salary Level)",
y = "人数",
fill = "離職 (1=Yes)") +
# 給与レベルをlow, medium, highの順に並べ替える
scale_x_discrete(limits = c("low", "medium", "high")) +
theme_minimal() +
theme(text = element_text(family = "notosansjp", size = 12))
https://gyazo.com/a6647d6ac4c6435dd868d520a62b0fd8
これは棒グラフを利用した傾向把握の例と言える。
課題:平均月間勤務時間(average_montly_hours)の分布を、離職者(left=1)と残留者(left=0)で比較してください
code:R
# 平均月間勤務時間の分布を離職の有無で比較
ggplot(df, aes(x = average_montly_hours, fill = factor(left))) +
# ヒストグラムを作成。position="identity"で棒を重ね、alphaで透明度を設定
geom_histogram(binwidth = 15, position = "identity", alpha = 0.6) +
labs(title = "勤務時間分布の比較:離職者 vs. 残留者",
x = "平均月間勤務時間 (Average Montly Hours)",
y = "人数",
fill = "離職 (1=Yes)") +
theme_minimal() +
theme(text = element_text(family = "notosansjp", size = 12))
https://gyazo.com/a4fd95f1291949999c93398532f1f1a2
これはヒストグラムを利用した傾向把握の例。
重なっていると見にくいかもしれない。横に並べるとしたらどのようなcodeを書けば良いか?
わからないときはGeminiに聞いてみよう!(日本大学では、googleのgemini有料版が使えます。大学のアドレスでログイン)
何がやりたいか明確なら、生成AIの支援はとても有益
https://gyazo.com/38e008361818c8f4f7b8b7ce16fb2031
課題:従業員の満足度(satisfaction_level)と最終評価(last_evaluation)の関係を、離職の有無(left)で色分けして、離職者の特徴的なクラスター(集団)があるかを探ってください。
code:R
# 満足度と評価の散布図を離職の有無で色分け
ggplot(df, aes(x = satisfaction_level, y = last_evaluation, color = factor(left))) +
# 散布図を作成。データ数が多いため、alphaを低く設定して密度の濃淡を表現
geom_point(alpha = 0.2, size = 1) +
labs(title = "満足度と評価の関係:離職の有無による層別化",
x = "満足度 (Satisfaction Level)",
y = "最終評価 (Last Evaluation)",
color = "離職 (1=Yes)") +
theme_minimal() +
theme(text = element_text(family = "notosansjp", size = 12))
https://gyazo.com/0c73d2b5744818f51f08c4ee14a3b289
課題:上の散布図は、離職者と残留者が重なっていて見にくい。離職者と残留者で、満足度 (satisfaction_level) と最終評価 (last_evaluation) の関係にどのような違いがあるか、データがどの領域に固まっているかを視覚的に把握したい。
code:R
# 満足度と評価の関係を離職の有無でファセット(分割)
ggplot(df, aes(x = satisfaction_level, y = last_evaluation, color = factor(left))) +
geom_point(alpha = 0.5, size = 1.5) +
# 離職の有無(0:残留, 1:離職)でグラフを分割
facet_wrap(~ left, labeller = as_labeller(c("0" = "残留者", "1" = "離職者"))) +
labs(title = "満足度 vs. 評価:離職の有無による比較",
x = "満足度 (Satisfaction Level)",
y = "最終評価 (Last Evaluation)",
color = "離職 (1=Yes)") +
theme_bw()
https://gyazo.com/e5e4cdbac195e10062a9980d94fd8e06
課題:離職者を特徴づける要因として、勤務時間と在籍年数の分布を比較したい。
code:R
# tidyverseパッケージを利用できるようにする。インストールされていなければ先にinstall.packages("tidyverse")を実行
library(tidyverse)
# 離職の有無でデータをフィルタリングし、勤務時間と在籍年数の密度プロットを重ねて表示
df_long <- df %>%
# 2つの量的変数を一つの列にまとめる(ggplotのfacetとdensityプロットのために整形)
pivot_longer(cols = c(average_montly_hours, time_spend_company),
names_to = "Variable",
values_to = "Value")
ggplot(df_long, aes(x = Value, fill = factor(left))) +
geom_density(alpha = 0.6) +
# 変数ごとにグラフを分割
facet_wrap(~ Variable, scales = "free") +
labs(title = "勤務時間・在籍年数の分布:離職者 vs. 残留者",
x = "値",
y = "密度",
fill = "離職 (1=Yes)") +
theme_light()
https://gyazo.com/c5e1479193358c68583320afd7c9f858
課題:部門や給与レンジといったカテゴリ変数が、離職率の高いグループの満足度にどのように影響しているかを分析したい
code:R
# 満足度 vs. 部門(離職者をハイライト)
ggplot(df, aes(x = sales, y = satisfaction_level, fill = factor(left))) +
geom_boxplot(position = position_dodge(width = 0.8)) +
labs(title = "部門別満足度と離職の関連",
x = "部門 (Department)",
y = "満足度 (Satisfaction Level)",
fill = "離職 (1=Yes)") +
# x軸のラベルが重ならないように傾ける
theme_bw() +
theme(axis.text.x = element_text(angle = 45, hjust = 1))
https://gyazo.com/702bb12f797dd8dfdfe9edc73f142936
課題:どの部門のどの給与レベルが最も離職リスクが高いのかを特定したい。部門と給与水準の組み合わせごとの離職率(left=1の割合)を計算し、ヒートマップで表示してください。
code:R
df_turnover_rate <- df %>%
group_by(sales, salary) %>%
summarise(
離職率 = mean(left, na.rm = TRUE), # left (0/1)の平均がそのまま離職率になる
人数 = n(),
.groups = 'drop'
)
df_turnover_rate <- df_turnover_rate %>%
mutate(
# salaryをファクター型に変換し、レベルを明示的に指定
salary = factor(salary,
levels = c("low", "medium", "high"))
)
ggplot(df_turnover_rate, aes(x = sales, y = salary, fill = 離職率)) +
geom_tile(color = "white", linewidth = 1) +
# 離職率をパーセント表示でテキストとして追加
geom_text(aes(label = scales::percent(離職率, accuracy = 1)), color = "black", size = 4) +
# 高い離職率を濃い色にする
scale_fill_gradient(low = "yellow", high = "red", labels = scales::percent) +
labs(title = "部門と給与水準による離職率ヒートマップ",
x = "部門 (Department)",
y = "給与水準 (Salary Level)",
fill = "離職率") +
theme_minimal() +
# 日本語フォントを適用
theme(text = element_text(family = "notosansjp", size = 12),
axis.text.x = element_text(angle = 45, hjust = 1))
https://gyazo.com/d3e96d9aa0e39a0d41078d82d1ae97d9
重回帰分析
課題:重回帰分析で、離職に影響を与える要因を考えてみよう。
まずは文字情報のsalesとsalaryをfactor化する。salaryは順序ファクター
code:R
df$sales <- factor(df$sales)
df$salary <- factor(df$salary,
levels = c("low", "medium", "high"),
ordered = TRUE)
重回帰分析を行う。被説明変数はleft
code:R
model1 <- lm(
left ~ satisfaction_level + last_evaluation + number_project +
average_montly_hours + time_spend_company + Work_accident +
promotion_last_5years + sales + salary,
data = df
)
summary(model1)
https://gyazo.com/66d8793a201db7427af157dc79279932
結果
満足度 (satisfaction_level)が1上がると離職確率が64%ポイント低下
最終評価(last_evaluation)が高いとわずかに離職しやすい(外部機会があるから?)
労働時間(average_montly_hours)は、月100時間増えると離職確率が6.4ポイント上昇(過重労働は確実に離職を押し上げる)
労災経験(Work_accident)は、労災経験者の方が離職しにくい(会社側の補償や配慮があるから?)
昇進(promotion_last_5years)は昇進経験があると離職確率が11ポイント低下
賃金(salary:ordered factor)は賃金が1ランク上がると離職確率が14%ポイント低下
線形確率モデルの課題
線形確率モデルでは予測値が0以上1以下に収まらないことがある
分散が不均一
次にlogit分析を行うのが一般的(ただし時間の関係で今回はここではやらない。このページの最下部に追加しておく)
主因子分析
他にどんな分析ができるのか?主成分分析をやってみる。
まずは離職者だけを対象とするデータを使う。
code:R
left_only <- subset(df, left == 1)
これまでの分析で重要性が高かった以下の変数を利用する
satisfaction_level
last_evaluation
number_project
average_montly_hours
time_spend_company
code:R
cluster_data <- left_only[, c("satisfaction_level", "last_evaluation",
"number_project", "average_montly_hours",
"time_spend_company")]
変数のスケールが違うため、必ず scale() する。
code:R
cluster_scaled <- scale(cluster_data)
クラスター数の候補を決める。
code:R
set.seed(123)
wss <- numeric(10)
for(k in 1:10){
wssk <- sum(kmeans(cluster_scaled, centers=k, nstart=20)$withinss) }
plot(1:10, wss, type="b",
main="Elbow Method",
xlab="Number of Clusters",
ylab="Within-group Sum of Squares")
https://gyazo.com/38dc36c896b30f692db53c02edf3e708
k-meansクラスタリングを実行する。以下はクラスターが三つのケース。
code:R
set.seed(123)
k3 <- kmeans(cluster_scaled, centers=3, nstart=25)
left_only$cluster <- as.factor(k3$cluster)
クラスターの特徴を要約する。
code:R
aggregate(cluster_data, by=list(Cluster=k3$cluster), FUN=mean)
https://gyazo.com/3597456afa953e22355975892499a034
簡潔に説明すると
https://gyazo.com/68dbe0a888fccf001eb49170e368452b
可視化(主成分分析PCA× クラスター)する。
code:R
library(ggplot2)
pca <- prcomp(cluster_scaled)
pca_df <- data.frame(pca$x,1:2, cluster=left_only$cluster) ggplot(pca_df, aes(x=PC1, y=PC2, color=cluster)) +
geom_point(alpha=0.6) +
labs(title="離職者クラスタ分布 (PCA可視化)") +
theme_minimal()
https://gyazo.com/b63e25a50a854bbe5430acebfe16c0b0
主成分分析とは、重要性が高い軸で自動的に整理する手法。しかしPC1とPC2がどのような軸かは知りたい。そこで主成分を調べる。
そのためにPCAロードings(成分負荷量)を見る。
これは「各変数がその主成分にどれだけ寄与しているか」を示している。
code:R
pca <- prcomp(cluster_scaled)
# 主成分の変数負荷量を確認
pca$rotation
https://gyazo.com/0d58e76b79b0642583b047946aab6da5
第1主成分(PC1: principal component1)=「仕事負荷・業務強度軸」
PC1を構成する負荷量(ローディング)は主成分を作るときの重み(係数)
その組み合わせを見ることで、それぞれの主成分の構成を理解できる
第2主成分=「心理的エンゲージメント(満足度) 軸」
そして寄与率を表示させる。
code:R
summary(pca)
https://gyazo.com/c49c9a19c0dfc31a6ec17507ffaeff62
PC1: 65.9%
PC2: 26.1%
累積:92.0%
よって第1主成分 + 第2主成分だけで 92% を説明できる
参考:ロジットモデル
線形確率モデルではなくロジットモデルを分析する
code:R
model_logit <- glm(
left ~ satisfaction_level + last_evaluation + number_project +
average_montly_hours + time_spend_company + Work_accident +
promotion_last_5years + sales + salary,
data = df,
family = binomial(link = "logit")
)
summary(model_logit)
https://gyazo.com/4fe0a40ac3d8d16dc0e34c7118f67c0c
https://gyazo.com/a2c1a35a4bf0b3fbfc940c72abbb4b02
https://gyazo.com/c1f435b35af76328712b64225dffab54
https://gyazo.com/dfff7180138f2f6af17a9eaa3268ca4a
全体まとめ
離職は、賃金や部署よりも 仕事の満足度(エンゲージメント)と業務負荷によって強く左右される。
特に、満足度の改善と昇進は、離職抑制において極めて大きな効果を持つ。
離職確率の予想は、企業にとってとても重要な課題のはず。派遣労働者の雇用継続について考える際にも、また製造業のワークエンゲージメント調査でも有用なはず。