0
0

米国テクノロジー業界の職場におけるメンタルヘルスデータセットを分析してみた。

Last updated at Posted at 2024-09-01

概要

テクノロジー業界における精神的健康が仕事に与える影響について分析した。
米国テクノロジー業界のメンタルヘルスアンケートのデータセットを使用し、機械学習を用いて影響する要因を抽出し改善策を提案する。
アンケート内容のwork_interfere(精神的問題が仕事に影響している頻度)を目的変数として、仕事に影響があるグループ、ないグループに分けた。
work_interfereから、テクノロジー業界の職場の4人に1人が精神的な問題を抱えている可能性があることが示唆された。このことから、精神的健康問題が仕事に影響を与えることを予防することが重要であると考える。
与えられた特徴量から、要因を個人的要因、労働環境要因、組織のメンタルヘルス資源要因、周囲のメンタルヘルス資源要因に分けて考えた。
それぞれを分析し、Catboostでのモデル学習から特徴量重要度・shap値を取得。分散分析・カイ二乗検定を用いて影響している特徴量について考察した。
考察から導かれた傾向と先行文献を参考に、以下の対策を提案するに至った。
1.個人は精神的問題についての家族の既往歴を把握し、解決に向けた治療を受ける行動をとる。
2.会社・組織は、健康増進プログラムについて話し合う機会を設け、助けを求める方法を個人が受けられるよう支援する。また、精神的問題に対して真摯に向き合うこと。
3.上司は部下に対して相談できる環境を用意すること。かつ、個人も上司に相談を行うよう意識すること。
この対策により、精神的健康問題が仕事に影響を与えることを予防することが期待できる。

はじめに

分析目的

米国テクノロジー業界のメンタルヘルスデータセットを用いて、テクノロジー業界における精神的健康が仕事に与える影響について考察し、機械学習を用いて影響する要因を抽出し改善策を提案する。

このデータ分析をしようと思った理由

私は現在医療職に就いている。
これからIT業界に足を踏み入れようとしているが、業界知識に乏しい。
業界理解の一環としてIT業界について分析を行いたいと思った。
また、データ分析について学んだことのアウトプットとしてドメイン知識のあるメンタルヘルス分野に焦点を当てた。

データセット選択理由

オープンデータを探したところ、日本のオープンデータでマッチするデータを拾えなかった。そのため、Kaggleに上げられていたデータセットを使用するに至った。
データ内容は、米国のテクノロジー業界におけるメンタルヘルスについてのアンケートである。

使用したデータ元:KaggleデータセットMental Health in Tech Survey

このデータセットから、テクノロジー業界において精神的問題が仕事に与える影響について考察できそうだと考えた。

精神的健康が仕事に影響を及ぼしている場合、その影響の要因は何かを分析し、改善するにはどうすればよいかの提案をしたいと思う。

背景調査

image.png
図:厚生労働省令和5年度 労働安全衛生調査(実態調査)結果の概要 (1

グラフの通り、情報通信業の常用労働者のうち、メンタル不調による連続1ヶ月以上の休業者数は他業種に比べ1.3%と高い割合である。一方退職者については0.3%と他業種と変わらない割合を示している。

休業者数が高いことから、情報通信業ではメンタルヘルスに関する対策が重要であると考える。

また、退職者数が他業種と変わらない割合であることは、メンタルヘルス不調が長期の休業にはつながっても、退職にはそれほど影響しない、または退職に至る前に何らかの対策や調整が行われている可能性がある。
その他、退職に至る原因はメンタルヘルス不調以外にあり、例えば、労働環境やキャリアパスの問題などが影響している可能性がある。

方法

データは予めクリーニングを済ませてある。
Qiita記事 : データのクリーニングをしてみた 参照

Age : 生産年齢人口から外れる10個の値を外れ値とみなし、外れ値以外の平均値で補完。
Gender : 自由記載で表記が乱れていたため、表記内容から読み取れるGenderをM,F,NBに統一。
State : 欠損値が存在するが、CountryでUnited Statesを選択した場合に回答されたものであるため無回答グループとして扱う。
self_employed : 被雇用者のデータを求めているため、この回答にYesと答えた者と欠損値18個を除外。
no_employees : 欠損値264個存在。欠損値量が多いため、無回答グループとして処置せず。
tech_company : テクノロジー業界のデータを求めているため、この回答にNoと答えた者を除外

使用したデータセットは、米国で作成されたテクノロジー業界の職場におけるメンタルヘルスに関するアンケートから作成されている。
母集団は米国のテクノロジー業界の被雇用者とし、その標本として1259人の回答が得られているとする。また、データのクリーニングを経て883人まで絞られる。
米国ではテクノロジー業界は全体の2%、約500万人が雇用されている。 (2
このことから、母集団を500万人と仮定する。
母集団500万人の場合標本サイズが883人であれば、95%の信頼水準での許容誤差は約3.5%となり、かなり良好な精度の結果が得られると考えられる。

データ理解と準備

データセットをVScode、Pythonを使用し可視化と分析を行った。

データセット概要

データの読み込み
df_mental = pd.read_csv('survey.csv')
df_mental

結果
image.png

データの行列確認
df_mental.shape
(1259, 27)
  • 1259行×27列のデータセット
カラム名 意訳・解釈 Dtype 変数尺度
Timestamp タイムスタンプ object 時系列変数
Age 年齢 int64 量的変数
Gender 性別 object 質的変数
Country object 質的変数
state object 質的変数
self_employed 自営業 object 質的変数
family_history 家族の既往歴 object 質的変数
treatment 治療 object 質的変数
work_interfere 仕事への影響 object 質的変数
no_employees 従業員数 object 量的変数
remote_work リモートワーク object 質的変数
tech_company テクノロジー会社所属 object 質的変数
benefits 福利厚生 object 質的変数
care_options ケアオプション object 質的変数
wellness_program 健康増進支援の有無 object 質的変数
seek_help 助けを求める方法の有無 object 質的変数
anonymity 匿名性 object 質的変数
leave 休暇 object 質的変数
mental_health_consequence 精神的健康と会社の反応 object 質的変数
phys_health_consequence 身体的健康と会社の反応 object 質的変数
coworkers 同僚 object 質的変数
supervisor 上司 object 質的変数
mental_health_interview 精神的健康と面接 object 質的変数
phys_health_interview 身体的健康と面接 object 質的変数
mental_vs_physical 精神的健康vs身体的健康 object 質的変数
obs_consequence 観測結果 object 質的変数
comments コメント object 質的変数
アンケート内容詳細

family_history
精神疾患の家族歴はありますか?
Do you have a family history of mental illness?
treatment
精神疾患の治療を受けたことがありますか?
Have you sought treatment for a mental health condition?
no_employees
あなたの会社または組織には何人の従業員がいますか?
How many employees does your company or organization have?
remote_work
少なくとも50%の時間はリモート(オフィス外)で働いていますか?
Do you work remotely (outside of an office) at least 50% of the time?
benefits
あなたの雇用主はメンタルヘルスの福利厚生を提供していますか?
Does your employer provide mental health benefits?
care_options
あなたの雇用主が提供するメンタルヘルスケアのオプションを知っていますか?
Do you know the options for mental health care your employer provides?
wellness_program
アンケート内容 : あなたの雇用主は、従業員の健康増進プログラムの一環としてメンタルヘルスについて話し合ったことがありますか?
原文 : Has your employer ever discussed mental health as part of an employee wellness program?
seek_help
あなたの雇用主は、メンタルヘルスの問題についてさらに学び、助けを求める方法を学ぶためのリソースを提供していますか?
Does your employer provide resources to learn more about mental health issues and how to seek help?
anonymity
メンタルヘルスや薬物乱用治療リソースを利用する場合、匿名性は保護されますか?
Is your anonymity protected if you choose to take advantage of mental health or substance abuse treatment resources?
leave
精神的な健康上の問題で病気休暇を取るのはどれくらい簡単ですか?
How easy is it for you to take medical leave for a mental health condition?
mental_health_consequence
雇用主とメンタルヘルスの問題について話し合うと、悪い結果が生じると思いますか?
Do you think that discussing a mental health issue with your employer would have negative consequences?
phys_health_consequence
雇用主と身体的な健康問題について話し合うと、マイナスの結果が生じると思いますか?
Do you think that discussing a physical health issue with your employer would have negative consequences?
coworkers
同僚とメンタルヘルスの問題について話し合う気はありますか?
Would you be willing to discuss a mental health issue with your coworkers?
supervisor
メンタルヘルスの問題について直属の上司と話し合う意思はありますか?
Would you be willing to discuss a mental health issue with your direct supervisor(s)?
mental_health_interview
面接で、採用候補者にメンタルヘルスの問題について話しますか?
Would you bring up a mental health issue with a potential employer in an interview?
phys_health_interview
面接で、潜在的な雇用主に身体的な健康問題について話しますか?
Would you bring up a physical health issue with a potential employer in an interview?
mental_vs_physical
あなたの雇用主は、心の健康を身体の健康と同じくらい真剣に受け止めていると思いますか?
Do you feel that your employer takes mental health as seriously as physical health?
obs_consequence
職場で精神疾患を持つ同僚による悪影響について聞いたり、目撃したりしたことがありますか?
Have you heard of or observed negative consequences for coworkers with mental health conditions in your workplace?

クリーニング後のデータ

df = pd.read_csv('df_mental_cleaned.csv')
df = df.drop(['Country','state'], axis=1)
df

image.png

Timestampはアンケート期間把握のために使用して削除。Contry,stateについては地域差を除外するため削除している。

探索的データ分析

work_interfer(目的変数)

df['work_interfere'].value_counts()
Sometimes    328
No answer    196
Never        149
Rarely       124
Often         86
Name: work_interfere, dtype: int64

有効回答 = 687人、無回答 = 196人
有効回答:無回答 ≒ 4 : 1

アンケートに回答をする人がメンタルヘルスに関心のある人と推測するにしても、精神的な問題を抱える人が問題のない人の4倍であることが分かる。
このことから、テクノロジー業界の職場の4人に1人が精神的な問題を抱えている可能性がある。

また、精神的な問題を抱える人の割合は、
Often86名(約 12.5%),Sometimes328名(約 47.7%),Rarely124(約 18.0%)名,Never149名(約 21.7%)
である。

Often,Sometimesを仕事に影響が出ているグループとして扱うと、精神的な問題を抱える場合60.2%の割合で仕事に影響が出ていることが分かる。

ここで、目的変数についてまとめたい。

このデータ分析の目的は仕事に影響が出ているグループの傾向の把握である。
Often,Sometimes,Rarely,Never,No answerのままでは細かく分類されており分かりにくい。
Often,Sometimesを仕事に影響が出ているグループとして扱うこととする。

また、質問内容から、回答がない場合は精神的影響がない人の回答と推測される。
Often,Sometimesを仕事に影響が出ているグループ(Yes)、Rarely,Never,No answerを仕事に影響が出ていないグループ(No)として扱うこととする。

def categorize_work_interfere(value):
    if value in ['Often', 'Sometimes']:
        return 'Yes'
    else:
    return 'No'

df['work_interfere'] = df['work_interfere'].apply(categorize_work_interfere)
df['work_interfere'].value_counts()
No     469
Yes    414

この操作により精神的影響があるかないかの比較は困難となったが、現在仕事に影響しているかしていないかの比較は容易になった。
また、クラス不均衡が解消されたこと、二値分類に整理されたことで分析内容が明確になった。


各特徴量の把握

Timestamp(タイムスタンプ)

  • おそらくアンケートの回答日時と思われる
  • このデータセットのアンケート期間は2014-08-27から2016-02-01までの523日間(約1年半)である。

量的変数

Age(年齢)

df_mental['Age'].describe()
count    1259.000000
mean       32.010326
std         7.117393
min        18.000000
25%        27.000000
50%        31.000000
75%        36.000000
max        62.000000
Name: Age, dtype: float64

ボリュームゾーンは30代を中心とした山になっている。

group1 = df[df['work_interfere'] == 'Yes']['Age']
group2 = df[df['work_interfere'] == 'No']['Age']

Levene検定により分散の等質性(等分散性)を確認する。

Levene検定
異なるグループ間での分散が等しいかどうかを検定するための統計的手法

# Levene検定の実行
stat, p_value = stats.levene(group1, group2)
print(f'Levene Statistic: {stat}, p値: {p_value}')
Levene Statistic: 0.019, p値: 0.887

p値が0.887であり、0.05よりもかなり大きいため、帰無仮説を棄却することはできない。
つまり、グループ間で分散に有意な差がないと判断でき、分散の等質性が確認できる。

正規分布を描き、分散の等質性が確認できるため、分散分析で目的変数とAgeのp値を算出する。

# 分散分析の実行
f_stat, p_value = stats.f_oneway(group1, group2)
print(f'F-statistic: {f_stat}, p-value: {p_value}')
F-statistic: 0.001, p値: 0.974

p値が0.974と0.05以上のため、統計的に有意な差があるとは言えない。
つまり、年齢の変化によりwork_interfereに与える影響はない可能性が高い。

質的変数

質的変数について、以下のコードで実施した。

質的変数可視化関数コード

df['work_interfere'] = pd.Categorical(df['work_interfere'],
                                             categories=['Yes', 'No'],
                                             ordered=True)

def crosstab(feature):
    cross_tab = pd.crosstab(df[feature], df['work_interfere'])
    return cross_tab

def plot_charts(feature):
    fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 6))
    
    # 棒グラフを描画
    ax1.bar(df[feature].value_counts().sort_index().index,
            df[feature].value_counts().sort_index().values, color='skyblue')
    ax1.set_title(f'Distribution of {feature}')
    ax1.set_xlabel(f'Number of {feature}')
    ax1.set_ylabel('Frequency')
    ax1.grid(axis='y', linestyle='--', alpha=0.7)
    
    # パーセント積み上げ棒グラフを描画
    cross_tab = pd.crosstab(df[feature], df['work_interfere'])
    df_percentage = cross_tab.div(cross_tab.sum(axis=1), axis=0) * 100
    order = ['Yes', 'No']
    df_percentage = df_percentage[order]
    
    colors = sns.color_palette('tab20', n_colors=len(df_percentage.columns))
    df_percentage.plot(kind='bar', stacked=True, figsize=(12, 8), color=colors, ax=ax2, edgecolor='none')
    
    ax2.set_title(f'Percent Stacked Bar Chart of Work Interference by {feature}')
    ax2.set_xlabel(feature)
    ax2.set_ylabel('Percentage (%)')
    ax2.legend(title='Work Interfere', loc='upper right')
    ax2.grid(axis='x', linestyle='--', alpha=0.7)
    
    plt.tight_layout()
    plt.show()

目的変数と各特徴量の関係についてカイ二乗検定を行う。

カイ二乗検定関数コード
def chi2_analysis_with_residuals(column_name, p_value_threshold=0.05):
    # クロス集計の作成(一応再度作成する)
    cross_tab = pd.crosstab(df[column_name], df['work_interfere'])  # 例として 'work_interfere' を使用

    # カイ二乗検定の実行
    chi2_stat, p_value, dof, expected = chi2_contingency(cross_tab, correction=False)

    # 結果の表示
    print("Cross Tabulation:")
    print(cross_tab)

    print("\nExpected Frequencies:")
    print(pd.DataFrame(expected, index=cross_tab.index, columns=cross_tab.columns))

    print("\nChi-Squared Statistic:", chi2_stat)
    print("P値:", p_value)
    print("自由度:", dof)

    # p値が指定された閾値未満の場合、残差の計算とヒートマップを実行
    if p_value < p_value_threshold:
        print(f"\nP値は {p_value:.4f} で0.05未満である。有意差が認められ、帰無仮説は棄却される。")
        # 標準化されたカイ二乗残差の計算
        observed = cross_tab.values
        residuals = (observed - expected) / np.sqrt(expected)

        residuals_df = pd.DataFrame(residuals, index=cross_tab.index, columns=cross_tab.columns)

        print("\nStandardized Residuals:")
        print(residuals_df)

        # ヒートマップの作成
        plt.figure(figsize=(10, 6))
        sns.heatmap(residuals_df, annot=True, cmap='coolwarm', center=0, fmt=".2f")
        plt.title(f'{column_name}の標準化カイ二乗残差のヒートマップ')
        plt.show()
    else:
        print(f"\nP値は {p_value:.4f} で0.05以上である。有意差が認められず、帰無仮説は棄却されない。")

帰無仮説(H0) : work_interfereと各特徴量の間に関連はなく、これらの変数は独立している。

有意水準 (α): 0.05(5%)

ex.family_history(性別)

plot_charts('family_history')
chi2_analysis_with_residuals('family_history')
結果
Cross Tabulation:
work_interfere  Yes   No
family_history          
No              189  362
Yes             225  107

Expected Frequencies:
work_interfere         Yes          No
family_history                        
No              258.339751  292.660249
Yes             155.660249  176.339751

Chi-Squared Statistic: 93.19310848145024
P値: 4.743298784570507e-22
自由度: 1

P値は 0.0000 で0.05未満である。有意差が認められ、帰無仮説は棄却される。

Standardized Residuals:
work_interfere       Yes        No
family_history                    
No             -4.314065  4.053222
Yes             5.557678 -5.221643

以下同様にカイ二乗検定を行った結果を一覧にする。

特徴量 p値 帰無仮説の棄却 標準化カイ二乗誤差
Gender 0.0581 棄却されない -
family_history 0.0000 棄却される image.png
treatment 0.0000 棄却される image.png
no_employees 0.169 棄却されない
remote_work 0.6040 棄却されない
benefits 0.019 棄却される image.png
care_options 0.000 棄却される image.png
wellness_program 0.005 棄却される image.png
seek_help 0.017 棄却される image.png
anonymity 0.109 棄却されない
leave 0.000 棄却される image.png
mental_health_consequence 0.000 棄却される image.png
phys_health_consequence 0.046 棄却される image.png
coworkers 0.979 棄却されない
supervisor 0.003 棄却される image.png
mental_health_interview 0.016 棄却される image.png
phys_health_interview 0.364 棄却されない
mental_vs_physical 0.000 棄却される image.png
obs_consequence 0.000 棄却される image.png

特徴量エンジニアリング

comments

追加のメモやコメント。

コメント部分だけcsv形式で抽出してある。
コメントは主にアンケートの追加の情報や調査に対する反応などが記載されている。
アンケートの内容が否定的か肯定的かで何か分かることがあるのではないかと考える。

そこで、感情分析を行いコメントをPOSITIVEとNEGATIVEに分けてみることとする。

df_comments = pd.read_csv('comments_emo.csv')

sentiment_pipeline = pipeline("sentiment-analysis")

# 感情分析を行う関数
def analyze_sentiment(text):
    if isinstance(text, str):
        result = sentiment_pipeline(text, truncation=True) 
        return result[0]['label'], result[0]['score']
    else:
        return 'UNKNOWN', None

# コメントの感情分析を実行し、結果を新しいカラムに追加
df_comments[['comments_emotion', 'confidence_score']] = df_comments['comments'].apply(lambda x: pd.Series(analyze_sentiment(x)))

# 結果を表示
df_comments[['comments', 'comments_emotion', 'confidence_score']]

image.png

これをcomments_emotionとしてdfに追加しcommentsを削除する。

df_comments_filtered = df_comments[['comments', 'comments_emotion']]
df = df.merge(df_comments_filtered, on='comments', how='left')
df = df.drop('comments', axis=1)
カイ二乗検定
Cross Tabulation:
work_interfere    Yes  No
comments_emotion         
NEGATIVE           64  22
POSITIVE           13  22

Expected Frequencies:
work_interfere          Yes         No
comments_emotion                      
NEGATIVE          54.727273  31.272727
POSITIVE          22.272727  12.727273

Chi-Squared Statistic: 14.936924537256765
P値: 0.00011116579716906025
自由度: 1

P値は 0.0001 で0.05未満である。有意差が認められ、帰無仮説は棄却される。

Standardized Residuals:
work_interfere         Yes        No
comments_emotion                    
NEGATIVE          1.253446 -1.658153
POSITIVE         -1.964811  2.599201

P値は 0.0001 で0.05未満である。有意差が認められ、帰無仮説は棄却される。


会社制度理解

['benefits', 'care_options', 'wellness_program', 'seek_help', 'anonymity']に関して、回答はNo,Yes,Don't know または Not sureである。

YesまたはNoと回答した場合、回答者は会社の制度に対して理解している可能性がある。
Noと回答した場合、回答者は会社の制度に対して不満を抱えている可能性がある。
Don't know または Not sureと回答した場合、回答者は会社の制度を理解していない可能性がある。
これらを考慮し、新しく特徴量を作成する。

columns_to_count = ['benefits', 'care_options', 'wellness_program', 'seek_help', 'anonymity']

# 'No' の数をカウントして新しい特徴量を作成
df['no_count'] = df[columns_to_count].apply(lambda row: (row == 'No').sum(), axis=1)

# 'Yes' の数をカウントして新しい特徴量を作成
df['yes_count'] = df[columns_to_count].apply(lambda row: (row == 'Yes').sum(), axis=1)

# 'Don't know' または 'Not sure' の数をカウントして新しい特徴量を作成
df['dont_know_count'] = df[columns_to_count].apply(lambda row: ((row == "Don't know") | (row == 'Not sure')).sum(), axis=1)
plot_charts('no_count')
chi2_analysis_with_residuals('no_count')
カイ二乗検定
Cross Tabulation:
work_interfere  Yes   No
no_count                
0                80  115
1                78   87
2                94  104
3                95   89
4                58   67
5                 9    7

Expected Frequencies:
work_interfere        Yes          No
no_count                             
0               91.426954  103.573046
1               77.361268   87.638732
2               92.833522  105.166478
3               86.269536   97.730464
4               58.607022   66.392978
5                7.501699    8.498301

Chi-Squared Statistic: 4.965105365114786
P値: 0.4201536238546113
自由度: 5

P値は 0.4202 で0.05以上である。有意差が認められず、帰無仮説は棄却されない。

P値は 0.4202 で0.05以上である。有意差が認められず、帰無仮説は棄却されない。

plot_charts('yes_count')
chi2_analysis_with_residuals('yes_count')
カイ二乗検定
Cross Tabulation:
work_interfere  Yes   No
yes_count               
0               158  226
1                79   93
2                75   54
3                54   34
4                28   34
5                20   28

Expected Frequencies:
work_interfere         Yes          No
yes_count                             
0               180.040770  203.959230
1                80.643262   91.356738
2                60.482446   68.517554
3                41.259343   46.740657
4                29.069083   32.930917
5                22.505096   25.494904

Chi-Squared Statistic: 19.70988765420588
P値: 0.0014164477206390096
自由度: 5

P値は 0.0014 で0.05未満である。有意差が認められ、帰無仮説は棄却される。
...
2               1.866718 -1.753850
3               1.983493 -1.863564
4              -0.198288  0.186299
5              -0.528061  0.496133

P値は 0.0014 で0.05未満である。有意差が認められ、帰無仮説は棄却される。

def categorize_yes_count(value):
    if value in [0, 1]:
        return '0-1'
    elif value in [2, 3]:
        return '2-3'
    else:
        return '4-5'

# カテゴリを変更
df['yes_count'] = df['yes_count'].apply(categorize_yes_count)

# 変更を確認
df['yes_count'].value_counts()
0-1    556
2-3    217
4-5    110
Name: yes_count, dtype: int64
plot_charts('dont_know_count')
chi2_analysis_with_residuals('dont_know_count')
カイ二乗検定
Cross Tabulation:
work_interfere   Yes   No
dont_know_count          
0                 97   89
1                138  133
2                 88   78
3                 51   92
4                 28   49
5                 12   28

Expected Frequencies:
work_interfere          Yes          No
dont_know_count                        
0                 87.207248   98.792752
1                127.060023  143.939977
2                 77.830125   88.169875
3                 67.046433   75.953567
4                 36.101925   40.898075
5                 18.754247   21.245753

Chi-Squared Statistic: 21.579164809548374
P値: 0.0006293560118239965
自由度: 5

P値は 0.0006 で0.05未満である。有意差が認められ、帰無仮説は棄却される。
...
2                1.152768 -1.083067
3               -1.959705  1.841215
4               -1.348413  1.266884
5               -1.559650  1.465348

P値は 0.0006 で0.05未満である。有意差が認められ、帰無仮説は棄却される。

def categorize_dont_know_count(value):
    if value in [0, 1, 2]:
        return '0-2'
    else:
        return '3-5'

# カテゴリを変更
df['dont_know_count'] = df['dont_know_count'].apply(categorize_dont_know_count)

# 変更を確認
df['dont_know_count'].value_counts()
0-2    623
3-5    260
Name: dont_know_count, dtype: int64

モデル構築

モデルを構築し、特徴量重要度やshap値から仕事に影響があるグループとないグループの要因を見出したい。

pycaretで使用するモデルを選択する。

from pycaret.classification import *
setup(data=df, target='work_interfere')
best_model = compare_models()
model = create_model('catboost') 
evaluate_model(model)

Accuracy: 0.7812と比較的良い性能がでるようだ。

Catboostを使用してモデルを構築していくこととする。

X = df_base.drop('work_interfere', axis=1)
y = df_base['work_interfere']

categorical_features = ['Gender', 'family_history', 'treatment',
       'no_employees', 'remote_work', 'benefits', 'care_options',
       'wellness_program', 'seek_help', 'anonymity', 'leave',
       'mental_health_consequence', 'phys_health_consequence', 'coworkers',
       'supervisor', 'mental_health_interview', 'phys_health_interview',
       'mental_vs_physical', 'obs_consequence', 'no_count', 'yes_count',
       'dont_know_count', 'comments_emotion']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

model = CatBoostClassifier(iterations=100, learning_rate=0.1, cat_features=categorical_features, early_stopping_rounds=50)

# モデルの学習
model.fit(X_train, y_train, verbose=100)
Accuracy: 0.7989
Precision: 0.7989
Recall: 0.7989
F1 Score: 0.7989
ROC-AUC Score: 0.8657128514056225

特徴量重要度とshap値を見ながらモデルにプラスに寄与する特徴量とマイナスに寄与する特徴量を比較しながら以下の特徴量を作成。

df_trial = df.copy()

df_trial['treatment*family_history'] = pd.factorize(df_trial['treatment'].astype(str) + df_trial['family_history'].astype(str))[0]
df_trial = pd.get_dummies(df_trial, columns=['treatment'], drop_first=True)

最終的に26個の特徴量を使用。

['Age', 'Gender', 'family_history', 'work_interfere', 'no_employees',
       'remote_work', 'benefits', 'care_options', 'wellness_program',
       'seek_help', 'anonymity', 'leave', 'mental_health_consequence',
       'phys_health_consequence', 'coworkers', 'supervisor',
       'mental_health_interview', 'phys_health_interview',
       'mental_vs_physical', 'obs_consequence', 'no_count', 'yes_count',
       'dont_know_count', 'comments_emotion', 'treatment*family_history',
       'treatment_Yes']

optunaでの探索範囲が絞れず、手動で地道にパラメータチューニングを行いあたりをつけ、グリッドサーチでパラメータを調整した。
以下内容で学習した。

X = df_trial1.drop(['work_interfere'], axis=1)
y = df_trial1['work_interfere']

categorical_features = ['Gender', 'family_history', 'no_employees',
       'remote_work', 'benefits', 'care_options', 'wellness_program',
       'seek_help', 'anonymity', 'leave', 'mental_health_consequence',
       'phys_health_consequence', 'coworkers', 'supervisor',
       'mental_health_interview', 'phys_health_interview',
       'mental_vs_physical', 'obs_consequence', 'treatment_Yes', 'comments_emotion', 
       'treatment*family_history', 'yes_count', 'no_count', 'dont_know_count']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

params = {'iterations' : 100,
          'learning_rate' : 0.08,
          'depth' : 5,
          'l2_leaf_reg' : 3,
          }

model = CatBoostClassifier(**params, cat_features=categorical_features, early_stopping_rounds=50)

# モデルの学習
model.fit(X_train, y_train, verbose=100)

モデル評価

最初のモデル

Accuracy: 0.7989
Precision: 0.7989
Recall: 0.7989
F1 Score: 0.7989
ROC-AUC Score: 0.8657128514056225

最初のモデルに特徴量選択・パラメータチューニング後の特徴量・パラメータを適用。

Accuracy: 0.8156
Precision: 0.8164
Recall: 0.8156
F1 Score: 0.8158
ROC-AUC Score: 0.8736194779116465

偽陰性(実際Yesであるが予測がNo)が減り陽性が増加。
若干良くなったか。

kfold交差検証(n_splits=5)を実施。

Fold 1: Accuracy: 0.8045, Precision: 0.8047, Recall: 0.8045, F1 Score: 0.8045, ROC-AUC Score: 0.8699
Fold 2: Accuracy: 0.7765, Precision: 0.7785, Recall: 0.7765, F1 Score: 0.7771, ROC-AUC Score: 0.8144
Fold 3: Accuracy: 0.7709, Precision: 0.7729, Recall: 0.7709, F1 Score: 0.7707, ROC-AUC Score: 0.8382
Fold 4: Accuracy: 0.7765, Precision: 0.7765, Recall: 0.7765, F1 Score: 0.7765, ROC-AUC Score: 0.7767
Fold 5: Accuracy: 0.7765, Precision: 0.7837, Recall: 0.7765, F1 Score: 0.7766, ROC-AUC Score: 0.8308

Final Results:
Accuracy: 0.7810 ± 0.0119
Precision: 0.7833 ± 0.0113
Recall: 0.7810 ± 0.0119
F1 Score: 0.7811 ± 0.0119
ROC-AUC Score: 0.8260 ± 0.0305

5フォールドのAccuracyの平均が0.7810、標準偏差が0.0119と各フォールドで大きな差は見られない。

モデルの性能は良い部類に入るだろう。

image.png

Shap値算出、可視化コード
import shap
shap.initjs()

# SHAP explainerの作成
explainer = shap.TreeExplainer(model)

# SHAP値の計算
shap_values = explainer.shap_values(X)

# SHAP値のデータフレームを作成
shap_values_df = pd.DataFrame(shap_values, columns=X.columns, index=X.index)

# 各特徴量のユニークな値に対するSHAP値の集計
feature_impact = {}

for feature in X.columns:
    if X[feature].dtype == 'object':  # カテゴリカル特徴量のみ処理
        unique_values = X[feature].unique()
        impact = {}
        for value in unique_values:
            # 特定の値にフィルタリングしてSHAP値の平均を計算
            mask = X[feature] == value
            filtered_shap_values = shap_values_df[mask][feature]
            impact[f'{feature}_{value}'] = filtered_shap_values.mean()  # 各SHAP値の平均を計算
        feature_impact[feature] = impact

# 集計結果をデータフレームに変換
impact_df = pd.DataFrame(feature_impact).T  # 転置して特徴量ごとの行を作成
impact_df = impact_df.reset_index()
impact_df = pd.melt(impact_df, id_vars=['index'], var_name='Feature Value', value_name='Average SHAP Value')

plt.figure(figsize=(20, 8))
sns.barplot(data=impact_df, x='Feature Value', y='Average SHAP Value', width=1, alpha=1)
plt.xlabel('Feature Value')
plt.ylabel('Average SHAP Value')
plt.title('Impact of Feature Values on Model Prediction')
plt.xticks(rotation=90)
plt.legend().set_visible(False)
plt.tight_layout()
plt.grid()
plt.show()

image.png

# SHAP値がプラスのデータをフィルタリング
positive_impact_df = impact_df[impact_df['Average SHAP Value'] > 0]
# 昇順でソート
sorted_positive_impact_df = positive_impact_df.sort_values(by='Average SHAP Value', ascending=False)
# 結果の表示
print("プラスのSHAP値を持つ特徴量とその値:")
sorted_positive_impact_df
図:プラスのShap値を持つ特徴量

image.png

# SHAP値がマイナスのデータをフィルタリング
negative_impact_df = impact_df[impact_df['Average SHAP Value'] < 0]
# 昇順でソート
sorted_negative_impact_df = negative_impact_df.sort_values(by='Average SHAP Value', ascending=True)
# 結果の表示
print("マイナスのSHAP値を持つ特徴量とその値:")
sorted_negative_impact_df
図:マイナスのShap値を持つ特徴量

image.png

考察

データについて考察をしていく。

アンケート内容に対して

Oftenは目的変数の中で一番頻度が少ない。
これは、仕事に頻繁に影響するほど精神的な健康上の問題がある場合、アンケートに答える気力も湧かないというケースが考えられる。また、すでに休職か退職をしてアンケートの回答機会がなかったという原因も考えられる。
このアンケートからは、今現在就業中かどうかの把握が難しい。
アンケート内に就業中・休職中・退職中などの回答欄があれば回答者の状況と問題の把握に繋がるのではないかと推測する。


特徴量について、グループに分けて考える。
個人的要因、労働環境要因、組織のメンタルヘルス資源要因、周囲のメンタルヘルス資源要因に分けられるのではないかと考えた。
各要因について深堀していく。

個人的要因

Age,Gender,family_history,treatment,は個人要因と言える。

Ageは分散分析からは有意差が認められなかった。

Gender円グラフ

2024年5月、日本のデータとなるが、ITエンジニアにおける女性比率は約20%(3 だそうだ。
M(男性)の回答率が圧倒的に高いが、データの男性比率80.2%,女性比率18.8%は納得できる値である。
Genderは男性の比率が多いため、FとNBのshap値がマイナスになったのだろう。

比率の差はあるが、カイ二乗検定から性別によって仕事に影響を与える精神的健康問題を抱えるかどうかの違いはみられないと考える。

モデルの特徴量重要度、shap値から、treatment,family_historyが重要な特徴量としてあげられる。
treatment、family_historyにはYesと答えた場合仕事に影響があるグループに属する傾向がみられる。

仕事に影響があるほど精神的健康に問題がある場合治療を受けている可能性は高くなるだろう。
精神疾患の遺伝的関係は傾向が強く出る疾患もあるが明確な関連性が証明されてはいない。ただ、家族歴の有無が回答者の精神的健康問題意識に影響を与えた可能性はあるのではないかと考える。

treatment,family_historyは精神的健康問題が仕事に影響があるかどうかを知る重要な手がかりとなりえるようだが、デリケートな問題であり個人が発信しない限り知ることは難しい。
個人が仕事への影響を自覚し主体的に対策を講じることが必要だろう。

労働環境要因

no_employees,remote_workは回答者の労働環境を反映しているといえる。

いずれもカイ二乗検定では仕事への影響があるグループとの関連は示唆されなかった。
特徴量重要度も低い部類に入っている。

従業員数の多さや働きやすさにより人間関係の複雑さ働きやすさが左右されるかと考えていたが、その可能性は低いようだ。

従業員数の多さやリモート環境が良いか悪いかというのは個人の捉え方に依存するからではないかと考える。

組織のメンタルヘルス資源要因

benefits,care_options,wellness_program,seel_help,anonymityは回答者の所属する組織のメンタルヘルス資源の有無を反映しているといえる。

anonymityについては仕事への影響との関連は示唆されなかった。

benefits(福利厚生),care_option(ケアオプションの提供の有無),について、仕事に影響のあるグループはYesと回答し、仕事に影響のないグループはDon't knowまたはNot sureと回答している傾向にある。

wellness_program(雇用主と健康増進プログラムについて話し合う機会があるか),seek_help(雇用主はメンタルヘルスの問題についてさらに学び、助けを求める方法を学ぶためのリソースを提供しているか)については、仕事に影響のあるグループはNoと回答、仕事に影響のないグループはDon't knowと回答している。

回答者の会社のメンタルヘルス資源・制度の理解が仕事に影響のあるグループとないグループの差ではないかと考えた。
そこで、5つの特徴量についてYes,No,Don't knowまたはNot sureと回答した数をそれぞれ数えて新たな特徴量を作成した。

ここから、Don't knowまたはNot sureと回答した数が少ないほど仕事に影響のあるグループに属し、多いほど仕事に影響のないグループに属する傾向が見られた。

以上から、wellness_program,seelk_helpがないと回答者が捉えている場合精神的健康問題が仕事に影響のあるグループに属しやすい傾向があるのではないかと考える。

また、仕事に影響のあるグループの回答者はその対策として会社の制度について理解しようと調べるためDon't knowまたはNot sureの回答が少なかったのではないだろうか。逆に仕事に影響のないグループは対策や制度の理解が必要ないためそもそも制度や資源を知らないという傾向になったのではないだろうか。

周囲のメンタルヘルスへの反応要因

leave,mental_health_consequence,phys_health_consequence,cowerkers,superviser,mental_vs_physical,obs_consequenceは周囲のメンタルヘルスへの反応と言える。

leave(休暇の取りやすさ)が難しいと感じるほど、仕事に影響のあるグループ、分からない場合仕事に影響のないグループに属する傾向がある。

仕事に影響のあるグループは休暇の取得を希望するが取りにくい状態のため耐えている可能性がある。
また、仕事に影響のないグループは休暇を希望しないため取得のしやすさについて分からないという傾向なのではないだろうか。

mental_health_consequence(精神的健康問題を伝えた場合悪い結果となるかどうか)にYesと答えた場合仕事に影響のあるグループ、Noと答えた場合仕事に影響のないグループに属する傾向がある。
phys_health_consequence(身体的健康問題を伝えた場合悪い結果となるかどうか)はMaybeと答えた場合仕事に影響のあるグループ、Noと答えた場合仕事に影響のないグループに属する傾向がある。
mental_vs_physical(組織は精神的健康を身体的傾向より重要視していると思うか)にNoと答えた場合、仕事に影響のあるグループに属する傾向がある。

精神的健康問題について話しにくい環境の場合精神的健康問題が仕事に影響を与えやすいのではないだろうか。
また、会社側が精神的健康問題を重要視しているかどうかということは、話しやすさにも関係してくるのだろう。

cowerkers(同僚と精神的健康問題について話をするか)についてはほとんどの回答者がSome of themと答え、仕事への影響との関連は見られなかった。
superviser(上司と精神的健康問題について話をするか)についてはsome of them,またはNoと答えた場合仕事に影響があるグループ、Yesと答えた場合仕事に影響のないグループに属する傾向がある。
obs_consequence(精神的問題について否定的な)にYesと回答した場合仕事に影響のあるグループに属する傾向がみられる。

obs_consequenceにNoと回答した人のうち、superviserにどう回答したかを調べる。

filtered_df = df[df['obs_consequence'] == 'No']
coworkers_responses = filtered_df['supervisor'].value_counts()
coworkers_responses
Yes             338
No              233
Some of them    209

obs_consequenceにYesと回答した人のうち、superviserにどう回答したかを調べる。

filtered_df = df[df['obs_consequence'] == 'Yes']
coworkers_responses = filtered_df['supervisor'].value_counts()
coworkers_responses
No              46
Some of them    43
Yes             26
Name: supervisor, dtype: int64

精神的健康問題については同僚よりも上司との関連が強くでた。
精神的問題について否定的な反応がある場合、上司に相談しにくく仕事に影響があるグループに属し、上司に相談しやすい場合は仕事に影響がないグループに属する傾向がみられる。

休暇が取りやすく、上司に相談できる環境、精神的問題に対する周囲の理解が重要であると言える。

先行研究との比較

この調査では、メンタルヘルス疾患で1ヵ月以上休職し、復職をしたITエンジニア7名を対象に、メンタル不調のきっかけや休職に至ったプロセスについて明らかにしている。

いずれも客先常駐の環境下で5つの要因が抽出されている。
このうち、

  • 客先常駐の中タイムリーに相談しづらい関係性
  • 関係性の中で業務をうまく遂行できない
  • 受け止めてもらえない不満や苛立ち

という3点について、本記事の周囲のメンタルヘルスへの反応要因からも同様のことが言える。

対策については、

  • 本人が問題に直面した際、解決に向けた考え方や行動がとれるようセルフケア研修を実践すること
  • 上司が部下に歩調を合わせ本音が話せる関係性を作ること
  • 孤立させないための連帯感や助け合いの職場風土を作ること
  • 上司や周囲の早期介入や対応を行うこと

という4つを挙げている。

この研究をもとに、本記事で行った分析について対策をまとめていきたい。

個人が行う対策として、精神的問題についての家族の既往歴を把握し、解決に向けた治療を受ける事。
会社が行う対策として、wellness_program,seelk_helpを整える事。また、周囲への精神的問題への理解も重要になってくるため、wellness_program,seelk_helpを整える事は精神的問題への理解の促進を図ることにもつながるだろう。
上司と話しやすい関係性を構築するということも対策に繋がるだろう。これは上司部下双方が関係性を良好に保つ意識が必要になる。

結論

目的
米国テクノロジー業界のメンタルヘルスデータセットを用いて、テクノロジー業界における精神的健康が仕事に与える影響について考察し、機械学習を用いて影響する要因を抽出し改善策を提案したい。

内容
与えられた特徴量から、要因を個人的要因、労働環境要因、組織のメンタルヘルス資源要因、周囲のメンタルヘルス資源要因に分けて考えた。
個人的要因は、treatmet,family_historyが影響していると分かった。これは個人が影響を理解して対策を取っていく必要がある特徴量である。
労働環境要因からは特に影響のある特徴量は認められなかった。
組織のメンタルヘルス資源要因は、wellness_program、seek_helpが影響していることが分かった。雇用主と健康増進プログラムについて話し合う機会を設け、雇用主はメンタルヘルスの問題についてさらに学び、助けを求める方法を学ぶためのリソースの提供をすることが重要である。
周囲のメンタルヘルス資源要因は、leave,supervisor,obs_consequenceが影響していることが分かった。休暇が取りやすく、上司に相談できる環境、精神的問題に対する周囲の理解が重要であると言える。

まとめ
1.個人は精神的問題についての家族の既往歴を把握し、解決に向けた治療を受ける行動をとる。
2.会社・組織は、健康増進プログラムについて話し合う機会を設け、助けを求める方法を個人が受けられるよう支援する。また、精神的問題に対して真摯に向き合うこと。
3.上司は部下に対して相談できる環境を用意すること。かつ、個人も上司に相談を行うよう意識すること。
精神的問題が仕事に与える影響に対する対策として以上3つを提案する。
本分析により、会社・組織での取り組みが必要となることが示唆された。

課題
日本のデータではなく米国のデータを使用した。日本におけるデータが同様の結果となるか不明である。
目的変数work_interfereと各特徴量との関係性は深堀できたが、その他の特徴量同士の関係性の分析が足りていないように思う。例えばAgeは特徴量重要度では比較的頻繁に使用されるが他の特徴量との関係性を見出すことができなかった。機械学習についてのさらなる理解を深めたい。

参考文献

(1 https://www.mhlw.go.jp/toukei/list/r05-46-50b.html
(2 https://coralcap.co/2023/01/reality-of-mass-layoffs/4
(3 https://librus.co.jp/it_career/new/stock/1110
(4 https://www.juntendo.ac.jp/assets/iryokangokenkyu14_1_03.pdf

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0