기업군집화 분석방법 기록

정밀의료바이오헬스 기업 분류 예시

초안. 대전 주축산업 기업군집화 프로젝트에서 사용한 상관행렬, 요인분석, 가중합 점수화, 기업군 분류 절차를 정밀의료바이오헬스 사례로 정리한다.
정책연구용역
Author

Sungjun Park

Published

2026-05-24

주의

초안입니다. 과거 프로젝트의 분석방법을 남기기 위한 기록이며, 원자료와 최종 결과 파일은 블로그에 포함하지 않습니다. 아래 코드는 모두 구조를 보여주는 예시이며 실행하지 않습니다.

library(data.table)
library(readxl)
library(ggplot2)
library(psych)

# 실제 경로는 문서에 남기지 않는다.
path_raw <- "data/raw_company_db.xlsx"
path_result <- "data/final_result.xlsx"

1 총평

이 분석은 기업의 재무·혁신 지표를 시장성혁신성이라는 두 축으로 축약한 뒤, 두 축의 상대적 위치로 기업군을 분류한 사례다. 단일 지표로 기업을 줄 세우지 않고, 상관행렬과 요인분석으로 지표 묶음을 확인한 점이 장점이다.

다만 원 코드는 산업별 반복 블록이 많고, 요인적재량 가중치를 행 번호로 참조하는 부분이 있다. 인수인계용으로는 변수명 기준 가중치 적용 방식이 더 안전하다.

2 분석 흐름

정밀의료바이오헬스 사례는 다음 순서로 진행했다.

  1. 기업 DB와 재무자료를 결합한다.
  2. 법인기업만 남기고 개인기업·대기업은 제외한다.
  3. 평균, CAGR, 비율지표를 만든다.
  4. 상관행렬로 후보 변수를 고른다.
  5. 아이젠밸류와 스크리도표로 요인 수를 정한다.
  6. 요인적재량으로 시장성·혁신성 점수식을 만든다.
  7. 백분위 기준으로 기업군을 분류한다.

3 요인분석

요인분석은 여러 관측변수 뒤에 숨어 있는 소수의 공통요인을 찾는 방법이다. 예를 들어 매출액, 종업원수, 자본금이 함께 움직인다면, 세 변수를 각각 따로 쓰기보다 시장성·규모라는 하나의 요인으로 요약할 수 있다.

\[ X_j = \lambda_{j1}F_1 + \lambda_{j2}F_2 + \cdots + \lambda_{jm}F_m + \epsilon_j \]

여기서 \(X_j\)는 관측변수, \(F_m\)은 공통요인, \(\lambda_{jm}\)은 요인적재량, \(\epsilon_j\)는 고유오차다. 요인적재량은 특정 변수가 특정 요인과 얼마나 강하게 연결되는지를 뜻한다.

flowchart LR
  A1[매출액] --> F1[시장성 요인]
  A2[종업원수] --> F1
  A3[자본금] --> F1

  B1[특허출원] --> F2[혁신성 요인]
  B2[특허등록] --> F2
  B3[R&D 성장] --> F2

4 1. 상관행렬

먼저 정밀의료바이오헬스 기업만 남긴 뒤, 기업-지표 행렬의 상관행렬을 계산했다. 이 단계에서는 다른 변수와 거의 연결되지 않는 고립 변수를 제외하고, 요인으로 묶일 가능성이 있는 변수만 후보로 남겼다.

cor_bio <- cor(
  df_bio[, -1],
  use = "pairwise.complete.obs"
)

varlist_bio <- cor_bio |>
  as.data.frame() |>
  tibble::rownames_to_column("variable") |>
  dplyr::mutate(
    dplyr::across(
      -1,
      ~ ifelse(.x == 1, NA, .x)
    )
  ) |>
  dplyr::rowwise() |>
  dplyr::mutate(
    max_abs_cor = max(
      abs(dplyr::c_across(-1)),
      na.rm = TRUE
    )
  ) |>
  dplyr::ungroup() |>
  dplyr::filter(max_abs_cor >= 0.5) |>
  dplyr::pull(variable)

5 2. 요인 수 결정

요인 수는 상관행렬의 아이젠밸류를 보고 정했다. 아이젠밸류는 각 요인이 설명하는 분산의 크기다. 보고서 흐름에서는 스크리도표에서 꺾이는 지점과 해석 가능성을 함께 보고 3개 요인을 선택했다.

cor_mat <- cor(df_bio_final[, -1])
eigen_value <- eigen(cor_mat)$values

scree_bio <- data.table(
  factor = seq_along(eigen_value),
  eigenvalue = eigen_value
)

ggplot(
  scree_bio,
  aes(x = factor, y = eigenvalue)
) +
  geom_point(size = 3) +
  geom_line(linewidth = 0.8) +
  labs(
    x = "요인 번호",
    y = "아이젠밸류"
  ) +
  theme_classic()

정밀의료바이오헬스의 최종 요인 해석은 다음과 같다.

요인 주요 적재 변수 해석
ML1 특허출원건수 평균, 특허등록건수 평균 혁신성과
ML2 연구개발비 CAGR, 연구개발비비중 CAGR 혁신투입 성장성
ML3 매출액 평균, 종업원수 평균, 자본금 평균 시장성·규모

6 3. 요인적재량과 가중치

요인분석은 psych::fa()를 사용했다. 회전은 varimax, 추정방법은 ml을 적용했다. 요인적재량 절대값이 0.5 미만인 값은 해석에서 제외했다.

fa_bio <- psych::fa(
  df_bio_final[, -1],
  nfactors = 3,
  rotate = "varimax",
  fm = "ml"
)

loading_bio <- fa_bio$loadings |>
  unclass() |>
  as.data.frame() |>
  tibble::rownames_to_column("variable")

변수 가중치는 같은 요인 안에서 요인적재량 합이 1이 되도록 표준화했다.

\[ w_{jk} = \frac{\lambda_{jk}}{\sum_j \lambda_{jk}} \]

최종 시장성·혁신성 점수에는 요인 수준 가중치도 적용했다. 요인 수준 가중치는 각 요인의 설명비율, 즉 아이젠밸류 기반 분산 설명력을 이용했다.

7 4. 점수식

먼저 모든 지표를 0-1 범위로 스케일링했다.

\[ x' = \frac{x - min(x)}{max(x) - min(x)} \]

정밀의료바이오헬스의 요인별 점수식은 다음 구조다.

\[ ML1 = 특허출원건수평균' \cdot w_{특허출원,ML1} + 특허등록건수평균' \cdot w_{특허등록,ML1} \]

\[ ML2 = 연구개발비CAGR' \cdot w_{R\&D,ML2} + 연구개발비비중CAGR' \cdot w_{R\&D비중,ML2} \]

\[ ML3 = 매출액평균' \cdot w_{매출,ML3} + 종업원수평균' \cdot w_{고용,ML3} + 자본금평균' \cdot w_{자본,ML3} \]

최종 축은 다음처럼 재조합했다.

\[ 시장성 = ML3 \cdot W_{ML3} \]

\[ 혁신성 = ML1 \cdot W_{ML1} + ML2 \cdot W_{ML2} \]

코드로 표현하면 아래와 같다. 원 코드의 행 번호 참조 방식보다, 변수명으로 가중치를 가져오는 방식이 더 안전하다.

df_bio_final <- df_bio_final |>
  dplyr::mutate(
    ML1 =
      특허출원건수_평균_스케일링 *
        w["특허출원건수_평균", "ML1"] +
      특허등록건수_평균_스케일링 *
        w["특허등록건수_평균", "ML1"],

    ML2 =
      연구개발비_CAGR_스케일링 *
        w["연구개발비_CAGR", "ML2"] +
      연구개발비비중_CAGR_스케일링 *
        w["연구개발비비중_CAGR", "ML2"],

    ML3 =
      매출액_평균_스케일링 *
        w["매출액_평균", "ML3"] +
      종업원수_평균_스케일링 *
        w["종업원수_평균", "ML3"] +
      자본금_평균_스케일링 *
        w["자본금_평균", "ML3"],

    시장성 = ML3 * factor_w["ML3"],
    혁신성 =
      ML1 * factor_w["ML1"] +
      ML2 * factor_w["ML2"]
  )

8 5. 기업 분류

시장성·혁신성 점수는 백분위로 바꿨다. 값이 작을수록 상위 기업이 되도록 1 - percent_rank()를 사용했다.

분류 기준
선도기업 시장성백분위 <= 0.1, 혁신성백분위 <= 0.1
예비선도기업 두 백분위가 모두 0.3 이하
잠재기업 두 백분위가 모두 0.5 이하
열외기업 시장성 또는 혁신성 백분위가 0.5 초과

선도기업은 2단계 조건을 추가로 적용했다. 최근 3년 평균매출액이 기준 이상이고, R&D 집약도가 기준 이상이거나 연구소를 보유해야 최종 선도기업으로 남겼다.

flowchart TB
  T["정밀의료바이오헬스 기업 분류"]

  T --> M{"시장성 백분위<br/>0.5 이하인가?"}
  M -- 아니오 --> O["열외기업"]
  M -- 예 --> I{"혁신성 백분위<br/>0.5 이하인가?"}

  I -- 아니오 --> O
  I -- 예 --> P{"시장성·혁신성<br/>모두 0.3 이하인가?"}

  P -- 아니오 --> L["잠재기업"]
  P -- 예 --> S{"시장성·혁신성<br/>모두 0.1 이하인가?"}

  S -- 아니오 --> E["예비선도기업"]
  S -- 예 --> C["선도기업"]

  C --> C2{"2단계 조건 충족?<br/>매출 규모 + R&D 기준"}
  C2 -- 예 --> C3["최종 선도기업"]
  C2 -- 아니오 --> E2["예비선도기업으로 조정"]

실제 보고서용 그림은 아래처럼 그릴 수 있다. 한글이 깨지는 경우에는 ragg 장치와 한글 폰트 계열을 명시한다.

knitr::opts_chunk$set(
  dev = "ragg_png",
  dpi = 144
)

theme_set(
  theme_classic(base_family = "Pretendard")
)

ggplot(
  df_bio_final,
  aes(
    x = 시장성백분위,
    y = 혁신성백분위
  )
) +
  geom_hline(
    yintercept = c(0.1, 0.3, 0.5),
    linetype = "dashed",
    linewidth = 0.35,
    color = "grey45"
  ) +
  geom_vline(
    xintercept = c(0.1, 0.3, 0.5),
    linetype = "dashed",
    linewidth = 0.35,
    color = "grey45"
  ) +
  geom_point(
    aes(color = 기업군),
    size = 2.4,
    alpha = 0.85
  ) +
  scale_x_reverse(limits = c(1, 0)) +
  scale_y_reverse(limits = c(1, 0)) +
  labs(
    x = "시장성 백분위",
    y = "혁신성 백분위",
    color = NULL
  ) +
  theme(
    legend.position = "bottom",
    text = element_text(family = "Pretendard")
  )

9 정리

이 분석은 요인분석 결과를 그대로 최종 결론으로 쓰기보다, 요인적재량과 아이젠밸류를 이용해 정책지표용 가중합 점수식을 만든 사례다. 핵심은 세 가지다.

첫째, 상관행렬과 스크리도표는 변수 선택과 요인 수 결정을 위한 탐색 단계다. 둘째, 시장성·혁신성은 원자료에 있는 단일 지표가 아니라 요인분석 기반의 재구성 지표다. 셋째, 기업군 분류는 산업 내부의 상대평가이므로 다른 산업과 백분위 값을 직접 비교하면 안 된다.