조용한 퇴사를 수치로 잡아라 — 실제 분석 사례로 보는 QQE Score 설계와 Python 구현


지난 포스트에서 인재가 떠나는 조직의 5가지 문화 신호를 다뤘다. 참여도 하락, 보상 불공정 인식, 성장 기회 부족, 리더십 불신, 업무 과부하 — 이 다섯 가지가 퇴사 이전에 조직 문화가 보내는 경보 신호라고 정리했다.

이번 글은 그 후속이다.

실제로 수천 명 규모의 조직 데이터를 가지고 이 신호들을 수치화해본 경험을 기반으로 작성한다. 조용한 퇴사를 어떤 변수로 측정할 수 있는지, 그 변수들을 어떻게 하나의 지수로 합산하는지, 그리고 그 지수를 어떻게 코드로 구현하는지를 순서대로 공개한다.

핵심 지표의 이름은 QQE Score(Quiet Quitting Engagement Score)다.


1. 왜 조용한 퇴사는 수치로 잡아야 하는가

Gallup의 2023년 글로벌 직장 현황 보고서에 따르면, 전 세계 직장인의 59%가 조용한 퇴사(Quiet Quitting) 상태다. 몸은 조직에 있지만 마음은 이미 떠난 상태. 최소한의 업무만 수행하며 실질적 기여를 철회하는 현상이다. 이 상태가 지속되면 2년 내 41%가 실제 퇴직으로 전환된다는 연구 결과도 있다.

문제는 이 현상이 눈에 보이지 않는다는 점이다. 출근은 한다. 보고서는 제출된다. 회의에도 참석한다. 그러나 조직에 대한 자발적 투입이 조용히 사라진다. HR이 이 신호를 감지하려면 설문 점수가 아니라 행동 데이터를 봐야 한다.

실제 분석을 진행한 조직의 규모는 전사 재직자 6,778명이었다. 이 분석에서 출발점이 된 질문은 하나였다. “지금 이 조직 안에서 심리적으로 이미 떠난 사람을 데이터로 찾을 수 있는가?”

결론부터 말하면 가능하다. 단, 제대로 된 변수 선택과 이론적 근거가 필요하다.


2. 조용한 퇴사의 학술적 메커니즘 — 4단계 이탈 이론

QQE Score를 설계할 때 가장 먼저 한 작업은 조용한 퇴사의 메커니즘을 학술 이론으로 정리하는 것이었다. 변수를 임의로 선택하는 것이 아니라, 각 변수가 왜 이탈 신호가 되는지 이론적 근거를 먼저 세웠다.

조용한 퇴사는 단일 사건이 아니다. 4개 차원이 순서대로 무너지는 과정이다.

1단계. 인지적 철수 (Cognitive Withdrawal) — Kahn(1990)

William Kahn은 직원 몰입을 “역할 수행에 자신을 얼마나 투입하느냐”로 정의했다. 몰입이 무너지는 첫 번째 신호는 인지적 분리다. 성과 목표에 대한 주도성이 사라지고, 역할을 “최소한”으로 재정의하기 시작한다.

측정 가능한 데이터 신호: 성과 추세 기울기 하락, 성장잠재력지수(GPI) 저하

2단계. 정서적 소진 (Emotional Exhaustion) — Maslach & Leiter(1997)

Christine Maslach는 번아웃을 소진(Exhaustion), 냉소(Cynicism), 효능감 저하(Inefficacy) 세 차원으로 구분했다. 정서적 소진이 쌓이면 정체감(Stagnation)이 굳어진다. “여기서 아무리 해봐야 달라지지 않는다”는 인식이 자리 잡는 것이다.

측정 가능한 데이터 신호: 정체지수 수치 상승, 전년 대비 정체감 악화 추세

3단계. 보상 형평성 붕괴 (Equity Perception) — Adams(1963)

John Stacey Adams의 공정성 이론은 직원이 자신의 투입(Inputs) 대비 산출(Outcomes)을 타인과 비교한다고 설명한다. 이 비율이 불공정하다고 인식되는 순간 두 가지 반응이 나타난다. 투입을 줄이거나, 관계를 떠나거나.

측정 가능한 데이터 신호: 이직위험지수(MRS) 상승, 보상 충격 이벤트 발생

4단계. 행동적 이탈 (Behavioral Disengagement) — Hirschman(1970)

Albert Hirschman의 Exit-Voice-Loyalty 이론에서 조용한 퇴사는 Voice(목소리)도 포기하고 Exit(이탈)을 준비하는 단계다. 이 단계에서 가장 직접적인 행동 신호가 나타난다. 내부 이동제도를 활용한 탐색 활동이 급격히 늘거나, 반대로 완전히 포기하고 외부만 바라보는 패턴이 그것이다.

측정 가능한 데이터 신호: 내부 이동 신청 이력, 최근 활동 빈도 가중치, 이탈 복합 행동 점수

이 4단계 메커니즘이 QQE Score의 설계 철학이다. 변수를 “그럴 것 같아서” 넣은 것이 아니라, 각 단계의 이론적 근거에서 도출했다는 점이 이 모델의 핵심이다.


3. QQE Score 4블록 구조 설계

4단계 이론을 4개 블록으로 구현했다. 각 블록의 가중치 합계는 1.0이다.

QQE Score = 인지적 철수 블록(25%) + 정서적 소진 블록(25%) + 보상 형평성 블록(15%) + 행동적 이탈 블록(35%)

블록 1 — 인지적 철수 (가중치 합계 0.25)

변수 가중치 사용 값 선택 이유
delta_perf_slope 0.15 변화량 성과 추세 기울기 악화가 철수의 객관적 지표
growth_potential_index (GPI) 0.10 실제값 현재 성장 가능성 수준을 직접 반영

GPI는 delta가 아닌 실제값을 사용한다. 이미 낮은 GPI에서 소폭 반등해도 delta는 양수가 되어 위험 신호가 희석되기 때문이다.

블록 2 — 정서적 소진 (가중치 합계 0.25)

변수 가중치 사용 값 선택 이유
stagnation 0.15 실제값 현재 정체감 수준 — “지금 얼마나 막혀있는가”
delta_stagnation 0.10 변화량 정체감 악화 추세 — “더 나빠지고 있는가”

실제값과 변화량을 병행한 이유: 정체감이 이미 높은 상태로 유지되는 것과 낮던 것이 급격히 올라가는 것은 서로 다른 위험 신호다. 하나의 지표만 쓰면 두 경우를 구분하지 못한다.

블록 3 — 보상 형평성 인식 (가중치 합계 0.15)

변수 가중치 사용 값 선택 이유
mobility_risk_score (MRS) 0.08 실제값 현재 이직 시장 내 위치를 직접 반영
compen_shock 0.04 이벤트값 보상 충격 트리거 발생 여부
mobility_shock 0.03 이벤트값 이동성 충격 트리거 발생 여부

MRS 실제값을 CPI delta 대신 사용한 이유: “지금 외부 시장에서 얼마나 매력적인 위치에 있는가”를 직접 보는 것이 Adams의 공정성 이론 — 외부 비교 대상 대비 자신의 처우 인식 — 에 더 충실하기 때문이다.

블록 4 — 행동적 이탈 (가중치 합계 0.35)

변수 가중치 사용 값 선택 이유
im_quiet_resignation_score 0.20 복합점수 내부 이동 탐색 기반 이탈 행동 복합 지수
im_growth_escape_score 0.08 복합점수 성장 도피 목적의 내부 이동 패턴
im_recency_weight_score 0.07 가중점수 최근 내부 이동 활동 빈도에 시간 가중 적용

행동적 이탈 블록에 가장 높은 비중(35%)을 둔 이유가 있다. 앞선 세 블록이 심리적 상태를 간접 측정한다면, 이 블록은 실제 행동을 직접 포착한다. 내부 이동제도를 통한 탐색 활동은 이직 의도가 실제 행동으로 전환됐다는 가장 강력한 신호이기 때문이다.


4. Python 구현 코드 전체

import pandas as pd
import numpy as np

# ── 가중치 정의 (4블록, 합계 = 1.0) ────────────────────

QQE_WEIGHTS = {

    # 블록 1. 인지적 철수 — Kahn(1990)
    'delta_perf_slope':            0.15,
    'growth_potential_index':      0.10,

    # 블록 2. 정서적 소진 — Maslach & Leiter(1997)
    'stagnation':                  0.15,
    'delta_stagnation':            0.10,

    # 블록 3. 보상 형평성 — Adams(1963)
    'mobility_risk_score':         0.08,
    'compen_shock':                0.04,
    'mobility_shock':              0.03,

    # 블록 4. 행동적 이탈 — Hirschman(1970)
    # IM = Internal Mobility (내부 이동제도 활동 기반)
    'im_quiet_resignation_score':  0.20,
    'im_growth_escape_score':      0.08,
    'im_recency_weight_score':     0.07,
}

assert abs(sum(QQE_WEIGHTS.values()) - 1.0) < 1e-9, \
    f"가중치 합계 오류: {sum(QQE_WEIGHTS.values())}"


# ── 변수 정규화 (Min-Max, 0~1 스케일) ──────────────────

def normalize_features(df: pd.DataFrame,
                        features: list) -> pd.DataFrame:
    df = df.copy()
    for col in features:
        col_min, col_max = df[col].min(), df[col].max()
        if col_max > col_min:
            df[col] = (df[col] - col_min) / (col_max - col_min)
        else:
            df[col] = 0.0
    return df


# ── QQE Score 계산 ───────────────────────────────────────

def compute_qqe_score(df: pd.DataFrame) -> pd.DataFrame:
    features = list(QQE_WEIGHTS.keys())
    df = normalize_features(df, features)

    # 전체 QQE Score
    df['qqe_score'] = sum(
        df[feat] * weight
        for feat, weight in QQE_WEIGHTS.items()
    )

    # 블록별 서브스코어 (원인 유형 분류용)
    df['qqe_block1_cognitive'] = (
        df['delta_perf_slope'] * 0.15
        + df['growth_potential_index'] * 0.10
    )
    df['qqe_block2_exhaustion'] = (
        df['stagnation'] * 0.15
        + df['delta_stagnation'] * 0.10
    )
    df['qqe_block3_equity'] = (
        df['mobility_risk_score'] * 0.08
        + df['compen_shock'] * 0.04
        + df['mobility_shock'] * 0.03
    )
    df['qqe_block4_behavioral'] = (
        df['im_quiet_resignation_score'] * 0.20
        + df['im_growth_escape_score'] * 0.08
        + df['im_recency_weight_score'] * 0.07
    )
    return df


# ── 위험도 분류 (분위 기반) ──────────────────────────────

def classify_qqe_risk(df: pd.DataFrame) -> pd.DataFrame:
    p90 = df['qqe_score'].quantile(0.90)
    p75 = df['qqe_score'].quantile(0.75)
    p50 = df['qqe_score'].quantile(0.50)

    conditions = [
        df['qqe_score'] >= p90,
        df['qqe_score'] >= p75,
        df['qqe_score'] >= p50,
    ]
    choices = ['즉각 개입', '고위험', '관찰']
    df['qqe_risk_tier'] = np.select(conditions, choices,
                                     default='정상')
    return df


# ── 아키타입 분류 (지배 블록 기준) ──────────────────────

def classify_archetype(row: pd.Series) -> str:
    blocks = {
        'A. 인지 철수형': row['qqe_block1_cognitive'],
        'B. 소진 정체형': row['qqe_block2_exhaustion'],
        'C. 보상 불만형': row['qqe_block3_equity'],
        'D. 행동 이탈형': row['qqe_block4_behavioral'],
    }
    return max(blocks, key=blocks.get)


# ── 내부 이동제도 활동 유무 확인 (해석 주의 대상 필터) ──

def flag_no_im_activity(df: pd.DataFrame) -> pd.DataFrame:
    df['has_im_activity'] = df['im_recency_weight_score'] > 0
    no_activity_rate = (~df['has_im_activity']).mean()
    print(f"내부 이동 활동 없는 구성원 비율: {no_activity_rate:.1%}")
    print("  → 이 구성원은 블록4 단독 해석 시 과소 추정 가능")
    return df


# ── 최종 실행 예시 ────────────────────────────────────────

# df = pd.read_csv('hr_analytics_raw.csv')
# df = compute_qqe_score(df)
# df = classify_qqe_risk(df)
# df = flag_no_im_activity(df)
# df['qqe_archetype'] = df.apply(classify_archetype, axis=1)
#
# 즉각 개입 대상 출력
# target = df[df['qqe_risk_tier'] == '즉각 개입']
# print(f"즉각 개입 대상: {len(target)}명")
# print(target[['employee_id','qqe_score','qqe_archetype',
#               'qqe_risk_tier']].sort_values('qqe_score',
#                                             ascending=False))

5. QQ Score — QQE의 다음 단계

QQE Score가 “이미 이탈을 탐색 중인가”를 측정한다면, QQ Score는 “누구를 먼저 개입해야 하는가”를 결정하는 최종 우선순위 지표다.

QQ Score = QQE Score × 0.6 + KTF Score × 0.4

KTF Score(Key Talent Flight Score)는 성과·보상·잠재력 기반으로 “얼마나 조직에 중요한 인재인가”를 측정한다. 조용한 퇴사 신호가 강하더라도 KTF Score가 낮은 직원과 높은 직원의 개입 우선순위는 달라야 한다.

def compute_qq_score(df: pd.DataFrame,
                     qqe_weight: float = 0.6,
                     ktf_weight: float = 0.4) -> pd.DataFrame:
    assert abs(qqe_weight + ktf_weight - 1.0) < 1e-9
    df['qq_score'] = (df['qqe_score'] * qqe_weight
                      + df['ktf_score'] * ktf_weight)
    return df

실제 분석에서 QQE Score가 높아도 KTF Score가 낮은 직원은 “관찰” 대상이 되고, 두 점수가 모두 상위에 있는 직원이 “즉각 개입” 대상이 됐다.


6. 실제 분석에서 나온 결과

6,778명 규모의 실제 데이터에 QQE Score를 적용한 결과는 다음과 같았다:

위험 구간 인원 비율 HR 대응 방향
즉각 개입 (상위 10%) 678명 10% QQE + KTF 모두 상위 — HRBP 직접 면담
고위험 (상위 25%) 약 1,700명 25% QQE 상위 사분위 — 관리자 모니터링
관찰 (상위 50%) 약 3,400명 50% 분기별 추세 추적
정상 약 3,400명 50% 현재 이탈 신호 없음

아키타입 분포에서 가장 많이 나온 유형은 “행동 이탈형(D)”이었다. 내부 이동제도 탐색 활동이 이미 행동으로 전환된 그룹이 가장 큰 비중을 차지했다. 그 다음이 “소진 정체형(B)”이었는데, 이는 번아웃으로 인한 정체감이 이미 굳어진 구성원이 상당수임을 보여준다.

모델의 예측력은 OOF AUC(Out-Of-Fold AUC, 교차검증 기반 예측 신뢰도) 기준 0.82였다. 실제 퇴직자를 비퇴직자보다 높게 예측할 확률이 82%라는 의미다. 학습에 사용한 실제 이탈 라벨 데이터가 94명 수준이라는 제약 안에서 이 수치는 유효한 성과다. 라벨 300건 이상 확보 시 AUC 0.85+ 달성이 다음 단계 목표다.


7. 설계 시 반드시 주의해야 할 3가지 함정

함정 1. delta값만 쓰면 “지금 상태”를 놓친다

GPI나 정체지수를 변화량(delta)으로만 측정하면 왜곡이 생긴다. 이미 GPI가 바닥인 상태에서 소폭 회복돼도 delta는 양수가 나와 “위험 없음”으로 분류된다. 실제값(현재 수준)과 변화량(추세)을 함께 사용해야 완전한 그림이 나온다.

함정 2. 내부 이동 활동 없는 구성원 과소 추정

내부 이동제도 활동 이력이 없는 구성원은 블록 4(행동적 이탈) 점수가 자동으로 낮게 나온다. 이를 “이탈 위험 없음”으로 해석하면 오판이다. 이미 내부 탐색 자체를 포기하고 외부만 보고 있는 구성원일 수 있다. 내부 이동 활동 보유 여부를 먼저 확인하고 해석해야 한다.

# 내부 이동 활동 없는 구성원 모수 확인
im_active_rate = (df['im_recency_weight_score'] > 0).mean()
print(f"내부 이동 활동 보유 구성원 비율: {im_active_rate:.1%}")
# 이 비율이 낮은 부서는 블록4 기반 해석에 주의 필요

함정 3. QQE를 성과 평가에 연결하는 순간 모델이 무너진다

QQE Score가 높다고 나쁜 직원이 아니다. 조직이 제공하는 환경이 그 사람을 떠나게 만들고 있다는 신호다. 이 점수를 개인 성과 평가나 인사 불이익에 연결하는 순간 구성원 신뢰가 붕괴되고, 데이터 자체가 오염된다. QQE는 개입의 근거이지, 평가의 근거가 아니다.


📌 이 글의 핵심 5가지

  1. 조용한 퇴사는 인지적 철수 → 정서적 소진 → 보상 형평성 붕괴 → 행동적 이탈의 4단계로 진행된다. QQE Score는 이 순서를 4개 블록으로 구현한 지수다.
  2. GPI와 정체지수는 delta가 아닌 실제값을 함께 써야 한다. 변화량만으로는 “현재 얼마나 위험한 상태인가”를 놓친다.
  3. 내부 이동제도 탐색 활동은 이직 의도가 행동으로 전환됐다는 가장 직접적인 신호다. 블록 4에 가장 높은 가중치를 둔 이유가 여기 있다.
  4. QQ Score = QQE × 0.6 + KTF × 0.4가 HR 개입 우선순위 최종 지표다. QQE 단독으로는 핵심 인재 여부가 반영되지 않는다.
  5. QQE는 평가 도구가 아니라 개입 도구다. 이 원칙이 흔들리는 순간 모델과 조직 신뢰 모두 무너진다.

맺음말 — 데이터가 먼저 알아챈다

이 분석에서 가장 인상적이었던 것은 숫자가 아니었다. 즉각 개입 대상으로 분류된 구성원 중 상당수가 “아직 아무 문제 없어 보이는” 직원들이었다는 점이다. 출근도 하고, 보고도 하고, 미팅에도 나온다. 그러나 데이터는 이미 6개월 전부터 신호를 보내고 있었다.

QQE Score는 그 신호를 읽는 언어다. 완벽한 모델이 아니다. 라벨 데이터가 쌓일수록 더 정교해지고, 개입 결과가 피드백될수록 더 신뢰할 수 있게 된다. 중요한 것은 지금 당장 완벽한 모델을 갖추는 것이 아니라, 데이터로 대화를 시작하는 것이다.

퇴사는 어느 날 갑자기 오지 않는다. 데이터는 항상 먼저 알고 있다.

 

[함께 읽으면 좋은 글]

위로 스크롤