그동안 연속형(continuous), 이분형(binary), 서열형(ordinal), 명목형(nominal) 변수별 다양한 유사성(Similarity) 측정, 혹은 비유사성(Dis-similarity)으로서 거리(Distance) 측정 방법을 여러개의 포스팅에 각각 나누어서 연재하였습니다.


그러면, 만약 여러개의 변수가 있는 데이터셋에 연속형, 이분형, 서열형, 명목형 변수가 혼합되어 섞여있다면 어떻게 유사성, 혹은 비유사성을 측정해야 할까요?


이처럼 여러 데이터 유형의 변수가 섞여있으면, 각 유형의 변수별로 유사성(혹은 비유사성, 거리)을 평가한 다음에, 이를 합하거나 평균을 내주면 됩니다.


이때 각 유형 변수별 유사성 척도는 특정 유형의 변수의 영향력이 동일하게 작용하도록 [0, 1] 사이의 값을 사용합니다. 이전 포스팅에서 살펴보았듯이 이분형(binary), 명목형(nominal), 서열형(ordinal) 변수의 유사성 (혹은 비유사성 거리) 값은 0 ~ 1 사이의 값을 가집니다.


반면, 연속형 변수의 거리 (가령, 맨하탄 거리, 유클리드 등)의 값은 [0, 1] 사이의 값이 아니므로, 각 연속형 변수의 범위(range = Xmax - Xmin)로 거리를 나누어 줌으로써 연속형 변수의 정규화된 거리가 [0, 1] 사이의 값으로 변환을 해준 후에 다른 범주형 변수의 거리와 더해주거나 평균을 내주면 됩니다.


아래의 예에서는 "이분형(binary) 변수"인 성별, "연속형(continuous) 변수"인 연령과 용돈, "명목형(nominal) 변수"인 직업, "서열형(ordinal) 변수"인 직업만족도의 5개 변수에 대해 관측치 1, 2, 3의 3명 간 비유사성인 거리(distance)를 계산해보겠습니다.




먼저, 성별은 이분형(이진형, binary) 변수이므로 두 관측치의 값이 같으면 거리는 '0', 두 관측치의 값이 다르면 거리는 '1'이 됩니다. (만약 유사성을 평가하는 거라면 반대로 두 관측치의 값이    같으면 유사성은 '1', 두 관측치의 값이 다르면 유사성은 '0'이 됩니다.)


위 예에서 dist_gender(x1, x2) 는 관측치 ID 1번과 ID 2번 관측치의 성별(gender) 이분형 변수에 대한 거리를 계산한 것으로서, ID 1번은 "남성"이고 ID 2번은 "여성"으로서 서로 다르므로 거리는 '1'이 됩니다. 반면, dist_gender(x1, x3) 는 관측치 ID 1번과 ID 3번 모두 "남성"으로서 서로 같으므로 거리는 '0'이 됩니다.


다음으로 연령(age)과 용돈(allowance)은 연속형(continuous) 변수로서, 두 관측치 간 거리를 계산했을 때의 값이 [0, 1] 사이의 값으로 정규화될 수 있도록 연령의 범위(range), 용돈의 범위로 맨하탄 거리(두 값 차이의 절대값)를 나누어주었습니다.


직업(job) 변수는 명목형(nominal) 변수이므로, 두 관측치의 값이 서로 같으면 거리는 '0', 서로 다르면 거리는 '1'이 됩니다. (유사성은 이와 반대임). 따라서 관측치 ID 1, ID 2, ID 3번이 모두 서로 간에 직업이 다르므로 관측치 쌍별 직업의 거리는 모두 '1'이 됩니다.


다음으로, 직업 만족도(job satisfaction)는 서열형(ordinal) 변수로서, 정규화한 순위(normalized rank)를 사용해서 [0, 1] 사이의 거리 값을 계산해줍니다.


마지막으로, 위의 혼합형(연속형, 이분형, 명목형, 서열형) 변수 5개(p=5)에 대해 두 관측치 간 각 변수별 거리를 계산한 값들을 평균하여 모든 변수를 종합하는 평균 거리를 계산하면,




입니다.


관측치 (ID 1번, ID 3번)이 평균 거리가 0.27 로서 (ID 1번, ID 2번)의 거리 0.54, (ID 2번, ID 3번)의 거리 0.62 대비 상대적으로 거리가 짧으므로 서로 유사하다고 평가할 수 있겠네요.


위의 이론적인 부분들을 R의 사용자 정의 함수로 표현해보면 아래와 같습니다.



# =======================================
# Distance measure for mixed variables
# : continuous, binary, ordinal, nominal
# =======================================


## 3 objects with variables of gender, age, allowance, job, and job_satisfaction
id <- c(1, 2, 3)
gender <- c('M', 'F', 'M')
age <- c(41, 24, 43)
allowance <- c(350000, 400000, 320000)
job <- c('consultant', 'designer', 'officier')
job_satisfaction <- c(4, 5, 3)


## range of continuous variable (age, allowance)

age_range <- 50
allowance_range <- 400000


## max rank of ordinal variable (job satisfaction)

max_rank <- 5

## Distance for Binary variable
dist_binary <- function(xi, xj){
  d = 0
  if (xi != xj){d = 1}
  return(d)
}

## Distance for Continuous variable
dist_continuous <- function(xi, xj, x_rng){
  d = abs(xi - xj)/x_rng
  return(d)
}

## Distance for Nominal variable
dist_nominal <- function(xi, xj){
  d = 0
  if (xi != xj){d = 1}
  return(d)
}

## Distance for Ordinal variable
dist_ordinal <- function(xi, xj, max_rank){
  d = abs(xi-xj)/(max_rank-1)
  return(d)
}

## Average of Distance for Mixed variables
dist_avg <- function(xi_id, xj_id, age_range, allowance_range, max_rank){
  d_gender = dist_binary(gender[xi_id], gender[xj_id])
  d_age = dist_continuous(age[xi_id], age[xj_id], age_range)
  d_allowance = dist_continuous(allowance[xi_id], allowance[xj_id], allowance_range)
  d_job = dist_nominal(job[xi_id], job[xj_id])
  d_job_satisfaction = dist_ordinal(job_satisfaction[xi_id], job_satisfaction[xj_id], max_rank)
  d_all <- c(d_gender, d_age, d_allowance, d_job, d_job_satisfaction)
  d_avg = mean(d_all)
 
  # print for detailed information
  print(paste0("**==== Distance of ID", xi_id, " and ID", xj_id, " ====**"))
  print("-----------------------------------------")
  print(paste0("Distance of Gender (Binary):", d_gender))
  print(paste0("Distance of Age (Continuous):", d_age))
  print(paste0("Distance of Allowance (Continuous):", d_allowance))
  print(paste0("Distance of Job(Nominal):", d_job))
  print(paste0("Distance of Job Satisfaction(Ordinal):", d_job_satisfaction))
  print("-----------------------------------------")
  print(paste0("Average Distance of ID", xi_id, " and ID", xj_id, ": ", d_avg))
  print("-----------------------------------------")
 
  return(d_avg)
}

## Distance b/w ID 1 and ID 2
dist_avg(1, 2, age_range, allowance_range, max_rank)
# [1] "**==== Distance of ID1 and ID2 ====**"
# [1] "-----------------------------------------"
# [1] "Distance of Gender (Binary):1"
# [1] "Distance of Age (Continuous):0.34"
# [1] "Distance of Allowance (Continuous):0.125"
# [1] "Distance of Job(Nominal):1"
# [1] "Distance of Job Satisfaction(Ordinal):0.25"
# [1] "-----------------------------------------"
# [1] "Average Distance of ID1 and ID2: 0.543"
# [1] "-----------------------------------------"
# [1] 0.543


## Distance b/w ID 1 and ID 3
dist_avg(1, 3, age_range, allowance_range, max_rank)
# [1] "**==== Distance of ID1 and ID3 ====**"
# [1] "-----------------------------------------"
# [1] "Distance of Gender (Binary):0"
# [1] "Distance of Age (Continuous):0.04"
# [1] "Distance of Allowance (Continuous):0.075"
# [1] "Distance of Job(Nominal):1"
# [1] "Distance of Job Satisfaction(Ordinal):0.25"
# [1] "-----------------------------------------"
# [1] "Average Distance of ID1 and ID3: 0.273"
# [1] "-----------------------------------------"
# [1] 0.273


## Distance b/w ID 2 and ID 3
dist_avg(2, 3, age_range, allowance_range, max_rank)
# [1] "**==== Distance of ID2 and ID3 ====**"
# [1] "-----------------------------------------"
# [1] "Distance of Gender (Binary):1"
# [1] "Distance of Age (Continuous):0.38"
# [1] "Distance of Allowance (Continuous):0.2"
# [1] "Distance of Job(Nominal):1"
# [1] "Distance of Job Satisfaction(Ordinal):0.5"
# [1] "-----------------------------------------"
# [1] "Average Distance of ID2 and ID3: 0.616"
# [1] "-----------------------------------------"
# [1] 0.616

 



이번 포스팅이 많은 도움이 되었기를 바랍니다.

행복한 데이터 과학자 되세요! :-)



728x90
반응형
Posted by Rfriend
,