pseudonymizer 0.1.8
pseudonymizer-to-protect-private-personal-data
License: MIT License
Requires Python: >3.6

# 가명처리∙결합 오픈소스 라이브러리 프로젝트

# 1. 배경 & 목적

- 라이브러리 주제 : 가명처리 및 가명정보 결합
- 제작 배경 :
  어떤 상품이나 서비스를 이용할 때 운영자에게 자신의 정보를 필수적으로 수집, 이용하고 더 나아가 조회, 제삼자 제공할 수 있도록 동의(전체 선택 체크)하는 단계를 지나갑니다.
  운영자가 이토록 제한적인 정보주체가 동의한 활용목적보다 유연하게 개인(신용)정보가 포함된 데이터를 쓸 수 있도록 특정인에 대한 식별가능성을 완화하는 조치(이하 '비식별 조치'라 합니다)를 할 수 있는, 파이썬 기반 오픈소스 데이터 가명처리 ∙ 결합 라이브러리를 만들었습니다.
- 제작 목적 :

# 2. 프로젝트 기간

- 기획 · 요건 정의 :
  (가명처리) 2023년 11월 04일 ~ 2023년 12월 2일
  (가명정보결합) 2024년 1월 6일 ~ 2024년 2월 17일
- 소스코드 구현 · 디버깅 :
  (가명처리) 2023년 11월 18일 ~ 2024년 4월 06일
  (가명정보결합) 2024년 2월 18일 ~ 2024년 4월 12일
- 테스트 ∙ 리팩터링 :
  (공통) 2024년 04월 01일 ~ 2024년 04월 21일
- Pypi 배포 :
  (1차) 2024년 04월 21일
  (2차) 2024년 04월 DD일

# 3. 담당 역할

### SY

- **역할과 책임**
  개인정보 속성에 (1) 삭제 · 대체 · 범주화하는 가명처리 기법과 (2) 프라이버시 보호 모델을 적용하고 (3) 비식별조치 성과지표를 측정하는 각 모듈 요건 정의 · 객체 구조 설계 · 소스코드 구현 · 테스트를 수행하였습니다.
- **성장한 경험**
  동질집합 · KLT모델 · 차분 프라이버시에 관한 통계학 · 컴퓨터공학 논문을 참고하여 가명처리 도메인을 반영한 실제 모듈을 개발하였습니다.
  MySQL 서버 DB 스키마와 파이썬 스크립트를 연동하여 SQL 쿼리를 통해 테이블 단위로 가명정보 결합 및 반출을 수행하는 객체 구조를 개선하는 리팩터링을 수행하였습니다.

### MJ

- **역할과 책임**
  가명처리된 데이터를 해시함수를 통해 암호화하고, 암호화된 결합키를 기준으로 SQL 상에서 가명처리된 데이터를 결합하는 모듈을 요건 정의 · 객체 구조 설계 · 소스코드 구현 · 테스트를 수행하였습니다.
- **성장한 경험**
  사용자 편의를 위해 모듈 개발 단계에서 추상화를 진행하고, 작업 단위를 분할하여 개발하였습니다.

# 4. 라이브러리 구성

## 4.1. 가명처리 기법 적용

### **4.1-0. 가명처리 기법 적용 총괄**

<details>
      <summary>제작 의도</summary>
    컬럼 1개에 여러 가지 가명처리 기법을 적용하여 가명처리를 유연하게 진행할 수 있도록 제작하였습니다.
  </details>
<details>
      <summary>결과</summary>
    가명처리 기법별로 클래스를 정의한 뒤, 정의된 클래스를 실행 클래스에 주입하여 가명처리 기법을 적용시켰습니다.
  </details>
<details>
      <summary>시행착오</summary>

- 구조 측면 :
  구체 클래스와 실행 클래스 사이 상하 관계를 유지하기 위해, 구체 클래스에 데이터가 입력되지 않고 오직 실행 클래스를 통해서만 데이터를 입력할 수 있도록 추상화하였습니다.
  </details>

### **4.1-1. 마스킹 기법**

<details>
      <summary>제작 의도</summary>
    특정한 개인을 알아낼 수 있는 고유식별정보 또는 개인정보를 복원할 수 없는 비가역적인 기법으로 원본을 대체하는 기법입니다.
    3글자 이상의 성명 · 주민등록번호 뒷자리 성별을 제외한 출생지 정보 6자리 · 사업자등록번호의 등록지역, 일련번호, 법인의 유형 중 일부 · 연락처 뒷자리 4자리를 특수 문자로 대체 · 주소 단위 변경에 의한 범위 축소 · 이메일 계정명 · 도메인 파트 특수 문자로 대체합니다.
  </details>
<details>
      <summary>결과</summary>
    6가지 마스킹 대상 정보타입 중 하나를 입력받아 특정 개인정보 속성 내 전체 레코드값을 특수문자로 대체 또는 단위를 축소합니다.
  </details>
<details>
      <summary>시행착오</summary>

- 실행 측면 :
  사용자에게 입력받은 데이터가 이메일과 주소 형식에 맞는 유효한 값인지 검증하는 정규표현식 구현을 하면서 예외 사례와 사유를 파악하는 과정에서 어려움이 있었습니다.
  ```python
  # AS-IS
  total_pattern = r"(\S+[시도])(\s+\S+[시군구])(\s+\S+[읍면동])"
  ```
  ```python
  # TO-BE
  total_pattern = r"(([가-힣]+(시|도)|서울|인천|대구|광주|부산|울산|대전|세종)\s*[가-힣]+(시|군|구)\s*[가-힣\d,.\s]+(읍|면|동|가|리))"
  CopyRightText: @ 2016 강인규 <techblog.woowahan.com/2505>
  ```
  최초 ‘시 또는 도’, ‘시 또는 군 또는 구’, ‘읍 또는 면 또는 동’ 중 각자 하나의 영역에 형식을 탐색하는 방법에서 각 행정구역을 인식하는 조건을 명확히 바꾸었습니다. 한글 주소라는 제약 조건과 광역시를 명시하는 방식으로 구체화하였습니다.
  개인정보 보호법상 배달 주문 취소 시 주소정보를 숨길 수 있는 패턴 설정을 통한 상세정보 추출 문서를 인용하였습니다.
- 구조 측면 :
  사용자가 직접 활용하는 유일한 함수(인스턴스 메서드), pseudonymizeData에서 각 주소 단위에 대한 마스킹 조건별 함수(클래스 메서드)의 기능을 불러오는 과정에서 고심하였습니다.
  0.0.1 초기 버전에서 업데이트 시 주소 체계가 변하는 예측하기 어려운 상황(예: 기준 확대/축소 등)을 고려하여 시군구과 읍면동/행정동 단위로 기능을 분리하였습니다.

- 도메인 측면 :
현실 세계의 주소 체계는 지번주소와 도로명주소로 이원화되어 있어 시군구, 읍면동(법정동)/행정동 단위 마스킹을 일괄 적용하는 기준 수립에 모호함이 있었습니다.
상세영역을 제외하고 행정구역 단위로만 마스킹하여 동질집합을 생성할 때 활용할 수 있도록 용도에 맞는 기준를 확정하였습니다. 도로명 주소는 시군구 단위 마스킹 기능만 적용하도록 주석에 명시하였습니다.
</details>

### 4.1-2. 랜덤 라운딩 기법

<details>
      <summary>제작 의도</summary>
    소득, 금융 또는 상거래·결제대금, 보유 상품·서비스 수 등 수치 또는 규모에 따라 특정인을 알아낼 수 있을 때를 대비하여 다른 값으로 대체합니다.
    rounding_type을 기준으로 (1) 올림 형식일 경우 1을 더하여 값보다 크거나 그대로 반환하여 같은 가장 가까운 정수로 대체합니다. (2) 내림 형식일 경우 값보다 작거나 같은 정수로 대체하니다.
    (3) 반올림 형식일 경우 소수점 이하의 값이 0.5 이상인 경우에는 올림, 그렇지 않은 경우에는 내림 처리합니다. (4) 맞춤형/경험적 반올림 형식일 경우[10의 자리 단위별 반올림 형식일 경우] 값의 10의 자릿수를 기준으로 반올림합니다.
  </details>
<details>
      <summary>결과</summary>
    개인식별정보 속성 내 전체 레코드 값을 올림, 반올림, 내림, 10의 자리 단위별 반올림 중 1가지 기법을 적용하여 낮은 자릿수의 값들을 일관성 있게 근삿값으로 변환합니다.
  </details>
<details>
      <summary>시행착오</summary>

- 실행 측면 :
개별 레코드 값의 자릿수 맞춤형 반올림을 하기 위해 원리를 정의하는 과정에서 생소함을 느꼈습니다.
값의 자릿수에 따라 10의 거듭제곱을 계산한 후 클래스 내부(지역공간)에 새로운 변수로 저장합니다. 깂을 10의 거듭제곱으로 나눈 수를 반올림합니다. 반올림한 값에 지역변수로 저장했던 10의 거듭제곱을 다시 곱한 값을 반환합니다.
  </details>

### 4.1-3. 연속형 변수 범주화 기법

<details>
      <summary>제작 의도</summary>
    연속형 변수(수치형 데이터)를 사용자가 입력한 임의의 수를 기준으로 범위를 설정하여 범주형으로 변환합니다.
    numeric_type을 기준으로 (1) 생년월일 형식은 현재 시점 기준으로 생일이 오기 전일 경우 1살을 차감하는 조건문을 설정하여 계산한 만 나이에 “3bin”, “5bin", “10bin" 중 입력받은 방식에 따라 연령 유형을 나눕니다. (2) 금액 형식은 입력받은 경계값 구간이나 그렇지 않은 경우 하드코딩된 경계값 구간에 따라 분위값을 할당합니다. (3) 사용자가 직접 경계값을 나누기 위한 구간을 튜플 타입으로 입력받아 수치를 새로운 유형으로 분류합니다.
  </details>
<details>
      <summary>결과</summary>

    (1) numeric_type에 “age”를 입력받을 경우 생년월일 정보(예: 주민등록번호 등)를 만 나이로 변환하여 5세 단위, 10세 단위, 초중후반으로 범주화합니다.
    (2) numeric_type에 “income”을 입력받을 경우 금액 정보를 경계값에 따라 분할하여 분위값으로 반환합니다. 단, 사용자가 경계값 리스트를 함수에 입력하지 않을 경우 2024년 건강보험료 1인 기준 소득분위를 기준으로 균등 분할합니다.
    (3) numeric_type에 “user_definition”을 입력받을 경우 사용자가 설정한 키에 유형을, 키에 대응되는 값에 구간 경계값을 기준으로 범주화합니다.

  </details>
<details>
      <summary>시행착오</summary>

- 실행 측면 :
  만 나이를 10대 단위로 초반, 중반, 후반으로 분류하는 기준을 설정하는데 모호함이 있었습니다. (예: 10대 초반/중반/후반, 20대 초반/중반/후반)
  3가지 연령대 기준별로 분류하는 과정에서 대소비교 연산자보다 시간을 단축할 수 있도록 나눗셈의 몫과 나머지를 활용하였습니다. 나이를 10으로 나눈 몫에서 3을 나눈 나머지 0, 1, 2로 구분하여 연령대 초·중·후반을 연산하였습니다.
  </details>

### 4.1-4. 문자형 변수 범주화 기법

<details>
      <summary>제작 의도</summary>
    문자형으로 저장된 정보에 대하여 상위의 개념으로 범주화합니다.
    category_type을 기준으로 (1) 날짜 형식은 입력받는 타입에 따라 일시를 삭제하고 연월로 반환하는 조건을 나눕니다. (2) 문자 형식은 키에는 범주화 이후의 유형을, 키에 대응되는 값에는 범주화 이전의 유형을 딕셔너리로 받아 특정 문자형 변수의 개별 레코드 값에 적용합니다.
  </details>
<details>
      <summary>결과</summary>
    (1) category_type에 “date"를 입력받을 경우 자격 취득일, 구독일, 연체일, 등록일자와 같이 개인과 관련된 날짜 정보를 연월일에서 연월 단위로 처리합니다.
    (2) category_type에 “user_definition"을 입력받을 경우 사용자가 범주화하기 위해 설정한 새로운 기준(키)과 대응되는 값(특정 범주에 속하는 문자열 리스트)을 기준으로 가명처리합니다.
  </details>
<details>
      <summary>시행착오</summary>

- 실행 측면 :
  사용자가 입력한 날짜정보의 타입이 일정하지 않다는 점에서 datetime.datetime 객체(YYYY-MM-DD hh:mm:ss)인 경우와 문자열(YYYYMMDD)인 경우를 구분하여 조건 설정하였습니다. 특히 문자열로 입력받은 날짜정보의 길이가 6자리, 8자리로 다를 수 있다는 점을 반영한 바있습니다.
  </details>

### 4.1-5. 연속형 속성 범주화 기법

<details>
      <summary>제작 의도</summary>
    특정 컬럼의 연속형 속성을 구간별 혹은 백분위별로 범주화합니다.
  </details>
<details>
      <summary>결과</summary>
    범주화할 컬럼명과 범주화 방법명을 입력받아 특정 컬럼 내 전체 레코드값을 주어진 기준에 따라 범주화합니다.
  </details>
<details>
      <summary>시행착오</summary>

- 실행 측면
  ```python
  def pseudonymizeAmountbyPct(self, dataseries, grouping_standard, right: bool, ascending: bool):
      rank_dataseries = dataseries.copy()
      rank_dataseries["rank"] = rank_dataseries.rank(method='min')
  ```
  입력받은 dataseries를 그대로 활용하는 과정에서 TypeError가 발생하여, 메서드 내부에서 활용되는 dataseries를 dataseries.copy()를 통해 복사하여 활용하였습니다.
- 구조 측면

  ```python
  class CategorizationOfColumn(Pseudonymizer):
    def __init__(self, numeric_type: str, grouping_standard, right: bool, ascending: bool):
        self.numeric_type = numeric_type
        self.grouping_standard = grouping_standard
        self.right = right
        self.ascending = ascending

    def pseudonymizeData(self, dataseries):
        """식별성이 높은 그룹을 하나로 묶는 메서드"""
        if self.numeric_type == "bin":
            return self.pseudonymizeAmountbyBin(dataseries, self.grouping_standard, self.right, self.ascending)
        elif self.numeric_type == "pct":
            return self.pseudonymizeAmountbyPct(dataseries, self.grouping_standard, self.right, self.ascending)
        else:
            raise ValueError(f"{self.numeric_type}은 유효한 범주화 기법 적용 유형이 아닙니다.")

  data = pd.cut(dataseries, bins = num, labels = grouping_standard, right = right)
  ```

  데이터프레임에서 컬럼 단위로 pd.cut을 활용하여 범주화하는 특성상, 개별 레코드값을 다루는 기존 연속형 변수 범주화와 별개의 클래스를 제작해야 했습니다.
  pd.cut 을 주 의존성으로 활용하면서, pd.cut에 입력값을 맞추고 그 입력값을 사용자가 직관적으로 이해할 수 있도록 구성하는 것이 어려웠습니다.
  </details>

### 4.1-6. 특정 그룹 속성 총계처리 기법

<details>
      <summary>제작 의도</summary>
    특정 컬럼을 동질집합별로 분류한 뒤, 해당 동질집합별 컬럼의 레코드값을 평균, 총합, 중간값, 최댓값, 최솟값 등 부분총계값으로 대체합니다.
  </details>
<details>
      <summary>결과</summary>
    총계처리할 컬럼명과 원하는 부분총계값의 종류 및 동질집합을 입력한 뒤, 원하는 종류에 따라 동질집합별로 pandas를 활용하여 부분총계를 연산합니다.
  </details>
<details>
      <summary>시행착오</summary>

- 구조 측면 :
  원래는 부분총계값 종류별로 메서드를 만든 뒤 파라미터에 따라 세부 메서드를 실행하도록 작성했는데, 연산 코드의 길이가 짧고 메서드별 연산 방식에 큰 차이가 없어 if문으로 파라미터별 분류를 진행하도록 변경하였습니다.
- 도메인 측면 :
  < 가명처리 가이드라인 > 에 제시된 총계값으로 평균, 총합, 중간값, 최댓값, 최솟값을 선정하였는데, 그 이외의 총계값 종류를 발굴하기 위해 자료를 찾았으나 찾지 못했습니다.
  </details>

## 4.2. 프라이버시 보호 모델 규정

### **4.2-0. 프라이버시 보호 모델 총괄**

<details>
      <summary>제작 의도</summary>
    개인정보 보호 수준을 통계적 기법을 활용해서 정량적으로 나타냅니다. 직접적으로 특정인이 식별되는 것뿐만 아니라 연결을 통해 식별되는 것도 방지하도록 합니다.
    가명처리 수준을 특정한 값(K, L, T, Epsilon 등)을 기준으로 조정할 수 있다는 점에서 재식별 위험을 완화하는 용도로 활용할 수 있습니다. 안전성만 평가하고 유용성을 평가하지 못하는 한계는 추후 가명처리 성과지표 모듈을 통해 보완한 바있습니다.
  </details>

### 4.2-1. 동질집합 생성

<details>
      <summary>제작 의도</summary>
    연령대, 거주 행정구역, 직업과 같은 개인식별가능정보 속성 조합을 동질집합으로 정의합니다. 예를 들어, [”20대 중반”, “서울특별시 동대문구“, “회사원”], [”20대”, “경기도 화성시 동탄중심상가1길”, “회사원”] 등을 속성값으로 가지는 개인들의 특성 집합을 의미합니다.
    동질집합을 만드는 과정에서는 [”나이”, “주소”, “직업”] 등 컬럼명을 입력하여, 판다스 데이터프레임의 groupby집계 함수를 통해 동질집합의 모든 경우의 수와 동질집합의 전체 속성에 대한 레코드 값들을 저장합니다.
    equivalent_class라는 딕셔너리를 클래스 내부에 캡슐화하면서(self.equivalent_class), 데이터를 조회하는 키로 튜플 타입의 동질집합 (”20대 중반”, “서울특별시 동대문구“, “회사원”)을 설정합니다. 동질집합 키로 조회되는 데이터는 개별 레코드의 속성값들이 아닌 데이터의 행 일련번호로 설정합니다.
  </details>
<details>
      <summary>결과</summary>
    self.equivalent_class에 동질집합을 키로 가지고 동질집합별 레코드의 인덱스를 값으로 하는 딕셔너리를 생성합니다. 딕셔너리는 {(”20대 중반”, “서울특별시 동대문구”, “회사원”) : [1388, 2933, 49455, 34548]} 의 형태를 갖습니다.
  </details>
<details>
      <summary>시행착오</summary>

- 실행 측면
  딕셔너리의 특성상 개인식별가능정보의 조합을 키로 저장하기 위한 자료형을 리스트(동적 타입)으로 받지 않기 때문에 튜플로 확정하는 과정에서 고심하였습니다.
  이진트리의 노드에 동질집합 키와 이에 대응하는 레코드의 인덱스를 저장하고 탐색하는 방식은 좀 더 구조가 복잡해지고 개인정보 데이터프레임에서 동질집합 속성값을 조회하기에 어려워지므로 채택하지 않았습니다.
  또한 개인식별가능정보의 조합이 1가지(단일값)이면 예외처리로 동질집합 리스트의 첫 번째 원소로 설정하였습니다.
  ```python
  for group, data in groupby_data:
      if len(group) > 1:
          key = group if len(group) > 1 else group[0]
          self.equivalent_class[key] = data.index.tolist()
  ```
  </details>

### 4.2-2. K-익명성 기법

<details>
      <summary>제작 의도</summary>
    개인 1명이 K명의 다른 사람 레코드와 구별되지 않도록 개인정보 노출에 대한 정량적인 위험성 K를 규정합니다.
    예를 들어 K가 3일 경우, [”20대 중반”, “수도권 거주”, “재직중”], [”30대 후반”, “호남 거주”, “실업”] 등 해당 데이터셋에 규정된 모든 동질집합에 해당하는 사람이 3명 이상이면 K-익명성을 충족한다고 볼 수 있습니다.
  </details>
<details>
      <summary>결과</summary>
    K개 이상의 값을 가진 동질집합은 인덱스 번호를 딕셔너리로 반환하고(추출 목적), K개 미만인 동질집합은 K에 미달하는 인덱스 수만 출력합니다. (추가처리 목적)
  </details>
<details>
      <summary>시행착오</summary>

- 도메인 측면 :
  개인정보를 가명처리한 사용자가 특정 유형의 집단이 K명 이상인 조건을 충족하는지 확인할 수 있도록 하기 위해 기능을 실행하는 객체에서 어떤 방식으로 값을 반환하면 좋을지 정답이 없어서 기준 수립에 어려움을 겪었습니다.
  집합별로 K익명성 기준을 충족하는 경우와 그렇지 않은 경우를 self.K_data와 self.sensitive_data로 구분하여 저장하였습니다. 사용자는 실제로 모듈을 활용할 때는 self.K_data는 반환값으로 저장하고 self.sensitive_data는 출력만 되는 형태로 구현하였습니다.
  L다양성 및 지역 L다양성, T근접성 기법에도 같은 방식을 적용하여 일관성을 확보하였습니다.
  </details>

### 4.2-3. L-다양성 및 지역 L-다양성 기법

<details>
      <summary>제작 의도</summary>
    [”20대 중반”, “수도권 거주”, “재직중”]과 같은 조건을 동질적으로 가지고 있는 집단 내 각 개인의 민감정보 속성값 종류는 최소 L개여야 하는 것을 L-다양성 조건이라고 규정합니다. 
    예를 들어, L이 3인데 해당 동질집단 내의 대출금액이 모두 [1억원, 1억원, 1억원, 2억 5천만원] 이라면, 민감정보 속성값의 종류는 2개이므로 L-다양성 조건을 충족하지 않는다고 볼 수 있습니다.
    (속성값: 신용평점, 대출금액, 거주하는 부동산의 계약 형태 등) 
    특정 동질집합의 민감정보 속성값 유형이 일부 레코드(행)에 집중되는 문제가 있어 전체적인 다양성을 확보할 수 있도록 속성값 유형별 집계되는 수의 최소한도인 Local L다양성을 규정합니다.
    예를 들어, [”20대 중반”, “수도권 거주”, “재직중”]과 같은 조건을 동질적으로 가지고 있는 대출 실행 고객 집단 내에서 연체일수가 [0, 0, 0, 0, 60, 60, 90] 이고 Local L다양성을 2로 규정합니다. 이 경우 연체일수가 90인 레코드값은 재식별 위험이 높다고 볼 수 있습니다. 이 경우 추가처리가 필요할 수 있습니다.
  </details>
<details>
      <summary>결과</summary>
    민감정보 속성값의 유형[민감정보 속성값의 유형별 집계값]이 L개 이상인 조건을 충족하는 동질집합은 인덱스 번호를 딕셔너리로 반환하고 L개 미만인 경우 그 동질집합의 인덱스 수만을 출력합니다.
  </details>
<details>
      <summary>시행착오</summary>

- 구조 측면 :
사용자가 K익명성 기법과 L다양성 기법, 두 모형을 동시에 적용할 경우 중복이 발생할 가능성이 높아 중복을 최소화하는 기능을 구현하고자 하였습니다.
프라이버시 보호 모델 모듈의 유연성을 제공하기 위해 동질집합 클래스의 상속 클래스인 K익명성 자식 클래스를 확장하여 L다양성 클래스를 손자 클래스로 상속하였습니다. 즉, 동질집합 생성과 K익명성 기법을 L다양성 기법에서 활용할 수 있도록 확장하였습니다.
  </details>

### 4.2-4. T-근접성 기법

<details>
      <summary>제작 의도</summary>
    사용자는 특성이 동질적인 집단의 개인정보를 추론을 통해 식별할 수 있는 가능성을 통계적으로 조정함으로써 정보가 특정한 값에 쏠려있지 않도록 합니다. 이는 각 동질집합별로 확률분포를 구한 뒤, 전체를 기준으로 특정 구간에 값이 몰려있는 경우 이상치로 판단하고 해당 경우의 레코드를 추가처리할 수 있도록 반환합니다.
    (추론 방식 : 다른 정보와의 결합 등)
    동질집합(개인식별가능정보[준식별자] 조합)을 상속받아 각 동질집합별로 민감정보 속성값에 대한 경험누적확률분포(ECDF)를 구합니다.
    전체 데이터의 민감정보 확률분포와 개별 동질집합의 민감정보 확률분포의 차이를 계산합니다.
    두 확률분포의 거리(Earth Mover’s Distance)의 범위에 대한 허용가능한 수준인 T(tolerance)을 정의하여 T근접성을 규정합니다.
  </details>
<details>
      <summary>결과</summary>
    전체 민감정보의 확률분포와 허용할 수 있는 범위인 T%(0~1) 이내 차이를 보이는 동질집합은 인덱스 번호를 딕셔너리로 반환하고 T%를 초과할 경우 그 동질집합의 인덱스 수와 분포의 차이(EMD)를 출력합니다.
  </details>
<details>
      <summary>시행착오</summary>

- 실행 측면 :
  전체 집단과 개별 동질집합의 민감정보 경험누적확률분포의 차이를 비교하기 위해 확률분포의 거리를 계산하는 과정에서 (1) 연속형 변수의 경우 비대칭한 분포의 길이를 조정하는 문제와 (2) 3가지 거리 유사도 중 의도에 맞는 방식을 선택하는 문제를 해결하면서 기준 수립에 어려움을 겪었습니다. 두 문제를 풀어낸 방법은 다음과 같습니다.
  (1)
  두 비대칭한 확률분포의 길이를 맞추기 위해 두 확률분포를 병합하여 하나의 배열에 오름차순 정렬합니다. 그 다음 병합한 데이터를 기준(sorted_merged_data)으로 기존 두 확률분포의 확률(sorted_qi_data, sorted_total_data) 중 비어있는 부분을 두 점 사이에 **선형 보간을 수행하여 새로운 점**을 찾습니다.

  ```python
  sorted_merged_data = np.unique(np.concatenate( (sorted_qi_data, sorted_total_data) ))
  qi_ecdf = np.interp(
  sorted_merged_data, sorted_qi_data, qi_distribution[group_key], left = 0, right = 1)
  total_ecdf = np.interp(
  sorted_merged_data, sorted_total_data, total_distribution, left = 0, right = 1)
  ```

  다만, 기존 좌표(확률분포 내 확률)가 병합된 데이터의 최솟값보다 작거나 병합된 데이터의 최댓값보다 클 때 반환할 값을 0에서 1 사이로 지정합니다. (left = 0, right = 1)
  (2)
  데이터 공간에 두 개의 확률분포 P(X), Q(X)가 있을 때, 두 확률분포 사이의 거리를 계산할 때 아래와 같은 3가지 방식 중 총 변동거리와 쿨백-라이블러 발산을 직접 구현하고 바슈타인 거리를 모듈을 활용하였습니다.
  최종적으로 거리가 0~1 사이의 값이 도출되어 사용자가 T% 이내로 범위를 규정할 수 있는 **총 변동 거리**를 채택하였습니다.

  - 총 변동 거리(Variation Distance) : 두 분포의 확률질량 또는 확률밀도 함수의 절댓값 차이의 최댓값을 말합니다.
  - 쿨백-라이블러 발산(Divergence) : 두 확률분포 P(X)와 Q(X) 사이의 비대칭적인 거리 측정 방법입니다. 한 확률분포를 사용해 다른 확률분포를 얼마나 잘 설명합니다.
  - 바슈타인 거리(Wassertein Distance) : 한 분포에서 다른 분포로 확률질량을 이동시키는데 필요한 최소 작업을 측정합니다.

- 구조 측면 :
  초기에는 자료형에 따라 경험누적확률분포를 다르게 구해야 한다는 점에서 자료형에 대한 입력변수를 통해 하나의 클래스 내에서 기능을 나누는 방식으로 구현하였습니다. 문자형 변수는 각 유형의 빈도를 기록하여 분포를 표현하는 반면, 연속형 변수는 값을 오르차순 정렬하여 누적된 분포 함수(이른바 히스토그램)으로 변환하여 분포를 표현합니다.
  다만, 연속형 변수와 범주형 변수는 확률분포뿐만 아니라 확률분포 간 거리를 구하는 과정도 다르다는 점에서 두 개의 클래스로 기능을 분리하였습니다. 즉, T근접성 기법의 객체 구조를 단순하게 바꾸어 가독성을 높이고 유지보수를 편하게 할 수 있도록 하기 위해 하나의 책임을 하나의 클래스에 한정하였습니다.
  </details>

### 4.2-5. 차분 프라이버시(Local Differential Privacy) 보호 기법

<details>
      <summary>제작 의도</summary>

데이터의 분포 특성을 고려하여 정보의 유용성은 유지하면서 개인정보를 보호하기 위해 이상치를 탐색하여 노이즈를 추가합니다.
동질집합 내 평균에서 양쪽 3표준편차의 범위 99.7%에 들지 않는 민감정보 행 번호만 별도로 추출함으로써 가설검정 기반에서 노이즈를 입력하기 위한 이상치를 선정합니다.
동질집합 내에서 이상치를 제외하기 전후 데이터가 동일한 확률분포(라플라스 분포 또는 정규분포)에 속할 확률의 비율(두 데이터 분포의 차이) 즉, **전역 민감도**를 계산합니다.
사용자 지정 개인정보 보호 수준인 하이퍼파라미터 엡실론을 입력받아 전역 민감도를 나눈 값 즉, **베타**를 계산합니다.
평균 0, 베타를 분산으로 가지는 확률분포에 속하는 **랜덤 난수를 추출**하여 이상치로 판정된 레코드값에 **노이즈**를 추가합니다.

  </details>
<details>
      <summary>결과</summary>
    동질집합 단위로 이상치를 판정한 후 통계적 난수를 더하거나 빼는 연산을 통해 차등적 정보보호 기능을 수행합니다.
  </details>
<details>
    <summary>시행착오</summary>

- 실행 측면
  각 동질집합별로 차분 프라이버시 보호 모델 적용하는 과정에서 판정된 이상치들(outlier_list) 중 대응되는 동질집합의 민감정보 속성값에서, 이상치 포함 전후 데이터를 저장하여 연산하는데 제약이 있었습니다.
  행 번호 리스트에서 이상치 인덱스를 제외한 후 데이터를 인덱싱하는 방식으로 간단하게 문제를 풀었습니다.

  ```python
  for i in outlier_list:
      upper_outlier = self._dataframe.iloc[i, col_num]
      group_data, exception_data = 0, 0

      group_list = self.equivalent_class[group_key]
      group_data = self._dataframe.iloc[group_list, col_num]

      group_list.remove(i)
      exception_list = group_list[:]
      exception_data = self._dataframe.iloc[exception_list, col_num]
  ```

- 구조 측면
  이상치를 판정하고 노이즈를 추가하는 과정에서 통계적 기능을 분리하여 객체 구조를 단순하게 구성하였습니다.
  즉, 연속확률분포의 모수(mu, beta or sigma)를 추정하는 기능 ∙ 확률변수가 특정한 값을 가질 확률을 나타내는 확률밀도함수 기능 ∙ 확률분포에 속할 확률과 비율을 계산하는 기능 ∙ 랜덤 난수를 산출하고 이상치에 추가하는 기능을 클래스 메서드로 선언합니다.
  </details>

## 4.3. 가명정보 결합

### 4.3-0. 가명정보 결합 총괄

<details>
      <summary>제작 의도</summary>

개인정보 보호법 등 관계 법령 및 개인정보 보호위원회의 < 가명정보 처리 가이드라인 > 등에 맞춰 가명정보 결합을 간편하게 구현할 수 있도록 하는 것을 목표로 삼았습니다.
아래 과정에서 활용되는 주요 요건은 다음과 같습니다.

- **결합키연계정보**
  동일한 결합키에 해당되는 서로 다른 일련번호를 매칭한 정보(매핑테이블)를 결합키연계정보라고 합니다.
  결합전문기관에서 서로 다른 개인정보처리자가 공통으로 보유하고 있는 정보를 이용하여 하나의 가명정보로 결합할 때 이용합니다.
- **결합키**
  결합신청자는 가명정보의 일부로서 해당 정보만으로는 특정 개인을 알아볼 수 없으나 다른 결합 대상 정보와 구별할 수 있도록 조치한 정보(암호화 키값)를 말합니다.
  결합키 생성 알고리즘은 결합키 생성 항목으로 특정 개인을 식별할 수 없도록 일방향 암호화 알고리즘(SHA256/384/512, HAS-160, XOR 등)을 사용합니다. 이 있습니다. 또한 결합신청자는 생성한 결합키의 표현방식(base64, hexa)을 결정하여야 합니다.
  개인정보의 안전성 확보조치 기준(개인정보 보호위원회 고시) 제7조(개인정보의 암호화) ② 개인정보처리자는 비밀번호 및 바이오정보는 암호화하여 저장하여야 한다. 다만, 비밀번호를 저장하는 경우에는 복호화되지 아니하도록 일방향 암호화하여 저장하여야 한다.
  예를 들어, 결합신청자가 동일하게 가지고 있는 성명, 연락처, 생년월일, 사업자등록번호 등의 개인식별정보를 결합키 생성 항목으로 지정할 수 있습니다. 또한 다국어가 포함될 경우 상호 동일한 인코딩 방식을 정하여야 합니다. (서로 다른 결합신청자 간 사전협의사항, 주민등록번호 활용 불가)
  민감정보와 고유식별정보에도 가명처리에 관한 특례조항이 적용되어 결합키로 활용할 수 있습니다. 다만, 개인정보 보호법 제24조의2에 의하여 주민등록번호의 경우 정보주체의 동의에 근거하여 처리하는 것이 허용되지 않으므로 정보집합물 결합을 위한 결합키로 주민등록번호를 이용하거나 결합키를 생성하기 위한 입력정보로도 활용할 수 없습니다.
- **결합에 필요한 일련번호**
  결합신청자는가명처리 대상 정보에 정보주체별로 중복되지 않는 일련의 값, 일련번호를 생성합니다. 결합신청자 X와 Y의 결합키는 같으며, 일련번호는 다릅니다.
- **결합대상정보**
  결합을 신청한 가명처리된 개인정보
- **추가정보**
가명정보를 원래의 상태로 복원하기 위한 정보를 추가정보라고 합니다. 결합키 생성에 이용된 암호화 알고리즘, 매핑테이블 등(접근권한, DB 분리보관 대상)이 있습니다.
추가정보가 필요한 경우 저장 시 암호화하여 저장하여야 합니다.
  </details>
<details>
      <summary>결과</summary>
    다수의 MySQL 서버 DB 테이블을 일방향 암호화된 키 연계정보를 기준으로 결합한 뒤, 결합신청자가 원본 테이블과 합쳐 반출하는 과정을 4개의 구조화된 모듈을 통해 구현하였습니다.
  </details>
<details>
      <summary>시행착오</summary>

- 도메인 지식 파악 후 모듈 기획, 요건 정의를 하는 과정에서 1달 반의 시간이 소요되었습니다.
  - 초기 일정 지연으로 인한 시간의 제약으로 2가지 부문의 기능은 프로젝트에서 제외한 바있습니다.
- pymysql을 기반으로 SQL 구문 입력을 통해 구조화된 코드를 작성하며, 해당 법령을 준수하는 레퍼런스를 찾기 어려웠습니다.
- 클래스와 메서드를 어느 범위까지 추상화하는 등 객체의 역할과 책임을 나누는 기준 수립에 어려움이 따라 이후 리팩터링에 1주를 소요하였습니다.
  </details>

### 4.3-1. 원본 테이블 분할 및 결합키 암호화

<details>
      <summary>제작 의도</summary>
    결합키 컬럼을 지정한 뒤, 원본 테이블을 DQL문 쿼리를 통해 지정된 컬럼으로 이루어진 결합키 테이블과 지정 컬럼이 제외된 결합대상정보 테이블로 분할합니다. 이 과정에서 DQL문 쿼리를 통해 원본 테이블에 식별 문자와 레코드 행번호를 합쳐 일련번호를 제작하여, 이후 결합 과정에서 사용합니다.
  </details>
<details>
      <summary>결과</summary>
    DB 연결정보와 원본 스키마/테이블명, 결합키 스키마/테이블명, 결합대상정보 스키마/테이블명을 입력받습니다. 이후 일련번호 부여, 결합키 테이블 제작, 결합대상정보 테이블 제작, 암호화된 결합키 제작 기능을 수행합니다.
    결합키 제작 시에는 결합키 테이블의 일련번호를 제외한 전체 컬럼 값과 메서드 이용 시 입력받은 SALT값을 합친 뒤, 해시 알고리즘을 이용하여 암호화된 결합키를 반환합니다. 이 과정에서 SHA256과 SHA512 알고리즘이 사용됩니다.
  </details>
<details>
    <summary>시행착오</summary>

- DB 스키마와 테이블을 정의하는 클래스를 만드는 과정에서 원본, 결합키, 결합대상정보 테이블 스키마와 테이블명을 묶는 중간 클래스를 만들고, 해당 중간 클래스를 기반으로 테이블 결합 등 실행 클래스를 만들었다가 수정했습니다.

  - DBContainer - 실행클래스 로 축약될 수 있는 구조를 DBContainer - InitTables - 실행클래스 로 더 복잡하게 만들면서 디버깅 및 코드 협업에 어려움을 겪었습니다.
  - 지나친 추상화가 협업 및 디버깅에 악영향을 끼치는 것을 알게 되었습니다.

  ```python
  # AS-IS : 아래의 InitTables를 통해 원본, 결합키, 결합대상정보 테이블을 저장하고, InitTables를 기반으로 실행 클래스를 제작
  class InitTables:
      """원본 스키마&테이블, 결합키 스키마&테이블, 결합대상정보 스키마&테이블 모으기"""
      def __init__(self):
          self.original_table = None
          self.key_table = None
          self.target_table = None
          self.serial_col = None
          self.serial_text = None
          self.key_cols = None
          self.join_key = None

      def addOriginalTable(self, original_table: TableContainer):
          """원본 스키마 & 테이블 입력 메서드"""
          self.original_table = original_table

      def addKeyTable(self, key_table: TableContainer):
          """결합키 스키마 & 테이블 입력 메서드"""
          self.key_table = key_table

      def addTargetTable(self, target_table: TableContainer):
          """결합대상정보 스키마 & 테이블 입력 메서드"""
          self.target_table = target_table

      def addSerialColInfo(self, serial_col: str, serial_text: str, join_key: str):
          """일련번호 컬럼명 (serial_col), 일련번호 내용 (serial_text1, serial_text2 형태) 입력 메서드"""
          self.serial_col = serial_col
          self.serial_text = serial_text
          self.join_key = join_key

      def addKeyColInfo(self, columns: list):
          """결합키 생성 항목 컬럼명 입력 메서드"""
          self.key_cols = columns


  # TO-BE : 타입 정의용 클래스는 DBContainer만 남기고, 실행클래스 내부에서 DBContainer의 위치를 달리하여 배치함
  class UpdateEncryptedKeyIntoDB(PyMySQLQuery):
    def __init__(self, pw: str, serverIP: str, port_num: int, user_name: str, database_name: str, kr_encoder: str,
                        main_tablename: str, key_schema: str, key_tablename: str,
                        target_schema: str, target_tablename: str):
        self.main_table = DBContainer()
        self.main_table.setSchemaTable(schema=database_name, table=main_tablename)

        self.key_table = DBContainer()
        self.key_table.setSchemaTable(schema=key_schema, table=key_tablename)

        self.target_table = DBContainer()
        self.target_table.setSchemaTable(schema=target_schema, table=target_tablename)


    def UpdateintoDBTables(self, key_table: list, key_column: str, serial_column: str, serial_text: str,
                                identifier_column, salt_value: str, salt_column: str, key_schema: str, hash_byte_type: str):
        self.updateSerialNumColumn.addSerialNumColumn(tables = self.main_table, serial_column = serial_column,
                                serial_text = serial_text,
                                identifier_column = identifier_column)

        self.insertKeyintoMainTable.insertKeyIntoDB(tables = self.main_table,
                                                    key_table_info = self.key_table,
                                                    key_table = key_table,
                                                    join_key = key_column,
                                                    salt_value = salt_value,
                                                    salt_column = salt_column,
                                                    serial_column = serial_column,
                                                    serial_text = serial_text)
  ```

  </details>

### 4.3-2. 매핑테이블 제작

<details>
      <summary>제작 의도</summary>
    일방향 암호화된 결합키 및 일련번호 컬럼을 가진 각 가명결합키 테이블을 로드한 뒤, 각 가명결합키 테이블의 결합키를 join key로 활용하여 각 일련번호 컬럼 간 내부 결합을 진행한다. 이후 내부 결결과일련번호 결합 뷰를 새로운 매핑테이블로 DB 내에서 업데이트합니다.
  </details>
<details>
      <summary>결과</summary>
    DB 연결정보와 결합키 테이블 스키마/테이블명 리스트, 새로 만들 매핑테이블 스키마/테이블명을 입력받은 뒤, 일련번호 컬럼들을 결합키 기준으로 합쳐 매핑테이블을 제작합니다.
  </details>
<details>
    <summary>시행착오</summary>
  현재 해당 클래스는 PyMySQLQuery 클래스를 상속받고, 상속받은 클래스를 인스턴스 단위에서 super()를 통해 초기화하여 DB를 조작합니다. 유저가 직접 불러오는 메서드가 아닌, 하위에서 작동하는 기능 구현용 메서드는 클래스 메서드로 구현하는 것을 원칙으로 하고 있습니다.
  그렇지만 인스턴스 단위에서 초기화되는 PyMySQLQuery 클래스 활용 DB 조작을 클래스 메서드를 통해 진행하는 것은 클래스 특성상 불가능했습니다. 따라서 해당 메서드는 부득이하게 일반 인스턴스 메서드로 구현하였습니다.

```python
class CreateMappingTable(PyMySQLQuery):
    def __init__(self, pw: str, serverIP: str, port_num: int,
                user_name: str, database_name: str, kr_encoder: str):
    super().__init__(pw = pw)
    super().connectDatabase(
        serverIP, port_num, user_name, database_name, kr_encoder)
    self.keytable_dictionary = {} # {key(키 스키마) : value(키 테이블)}
    self.mapping_table = None

    def checkKeyTableInfo(self, key_schemas: List, key_tablenames: List, joinkey_column: str, serialnum_columns: List):
        """결합키 테이블에 내부 조인 키와 일련번호 컬럼이 있는지 검증하는 메서드"""
        for i in range(len(key_schemas)):
            check_query = f"SELECT 1 FROM {key_schemas[i]}.{key_tablenames[i]} WHERE {joinkey_column} IS NOT NULL AND {serialnum_columns[i]} IS NOT NULL"

            # 검증 쿼리 실행
            super().dataQueryLanguage(check_query)
            result = super().executeQuery()

            # 검결과인
            if result:
                print(f"{key_schemas[i]}.{key_tablenames[i]} 테이블은 필요한 내부 조인 키와 일련번호 컬럼을 가지고 있습니다.")
                return True
            else:
                print(f"{key_schemas[i]}.{key_tablenames[i]} 테이블은 필요한 내부 조인 키와 일련번호 컬럼을 가지고 있지 않습니다.")
                return False
```

</details>

### 4.3-3. 결합대상정보 결합

<details>
      <summary>제작 의도</summary>
    매핑테이블의 일련번호에 각 결합대상정보 테이블의 일련번호를 기준 삼아, 각 결합대상정보를 매핑테이블에 결합시켜 최결과테이블을 제작합니다.
  </details>
<details>
      <summary>결과</summary>
    DB 연결정보와 결결과이블 스키마/테이블명과 매핑테이블 스키마/테이블명, 결합대상정보 테이블 스키마/테이블명 리스트, 일련번호 컬럼명 리스트를 입력받은 뒤, 최종 결결과이블을 제작합니다.
  </details>
<details>
    <summary>시행착오</summary>

- 원래는 `CREATE TABLE tablename AS SELECT * from table1 INNER JOIN table2 ON table2.target1 = mapping_table.target1` 형태로 결결과테이블을 만들고 싶었으나, 해당 구문 실행 시 INNER JOIN에 필요한 매핑테이블의 컬럼명과 결합대상정보 테이블의 컬럼명이 중복되는 것으로 판정되어 오류가 발생했습니다.
- 해당 문제를 해결하기 위해 SELECT \* 대신, 모든 컬럼명을 중복 없이 나열하도록 코드를 작성하였습니다.

  ```python
  @classmethod
  def createSelectQuery(cls, mapping_schema: str, mapping_tablename: str, target_schemas: List, target_tablenames: List, target_join_columns: List):
      """매핑테이블에서 결합대상정보 테이블의 일련번호 컬럼을 조인키로 활용하여 2개 이상의 결합대상정보 테이블을 병합하는 쿼리 중 조회 구문을 반환하는 메서드"""
      select_dql = "SELECT "
      for i, (schema, table) in enumerate(zip(target_schemas, target_tablenames)):
          for column in target_join_columns[i]:
              select_dql += f"{schema}.{table}.{column} AS {i+1}_{column}, "
      select_dql += f"{mapping_schema}.{mapping_tablename}.*"
      from_dql = f"FROM {mapping_schema}.{mapping_tablename} "

      return select_dql + from_dql

  ```

- 해결과만들어지는 쿼리는 다음과 같습니다.
  ```sql
  CREATE TABLE FINANCIALCIONSUMER.FINANCE_RETAIL_TARGET AS
    SELECT FINANCIALCONSUMER.FINANCE_TARGET.NUM_SERIAL AS 1_NUM_SERIAL,
    SELECT FINANCIALCONSUMER.FINANCE_TARGET.ZIP_CODE AS 1_ZIP_CODE,
    SELECT FINANCIALCONSUMER.FINANCE_TARGET.HOME_ADDRESS AS 1_HOME_ADDRESS,
    ...
  INNER JOIN FINANCIALCONSUMER.FINANCE_TARGET
    ON FINANCIALCONSUMER.FINANCE_TARGET.SERIALNUM_FINANCE = FINANCIALCONSUMER.FINANCE_RETAIL_MAP.SERIALNUM_FINANCE
  ```
  </details>

### 4.3-4. 결합대상정보 원본과 합쳐 반출

<details>
      <summary>제작 의도</summary>
    결합신청자가 최종 결합되지 않결과 결합결과 모두 받아볼 수 있도록, 최종 결결과테이블에 원본 결합대상정보 테이블의 일련번호를 기준으로 LEFT OUTER JOIN 하여 두 테이블을 합쳐 반출용 테이블을 제작합니다.
  </details>
<details>
      <summary>결과</summary>
    DB 연결정보와 최종 반출 예정 스키마/테이블명, 결결과이블 스키마/테이블명, 함께 반출할 기존 테이블 스키마/테이블명, 일련번호 컬럼명을 입력받은 뒤, 반출용 테이블을 제작합니다.
  </details>
<details>
      <summary>시행착오</summary>

- 도메인 측면
  기존 테이블과 결결과테이블을 join하는 과정에서 어떤 쿼리를 사용해야 효과적일 지 고민했고, 결과EFT OUTER JOIN을 이용하는 것으로 결정하였습니다.
  </details>

## **4.4. 비식별조치 성과지표**

### 4.4-0. 유용성 지표

<details>
      <summary>제작 의도</summary>

사용자가 가명처리 수준을 인식할 수 있도록 가명처리 기법이 적용된 개인정보(가명정보)의 유용성에 대한 정량화된 지표를 측정합니다. 해당 유용성 지표는 원본 데이터와 가명처리 기법 적용 데이터가 유사할수록 유용하다고 판단합니다.
parameter를 기준으로

(1) "cs"일 경우 가명처리 전후 연속형 벡터(속성) 간 각도를 이용하여 서로 얼마나 유사한지 벡터 스칼라곱을 정규화된 두 벡터로 나눈 값(코사인 유사도)을 계산합니다.
(2) “mc"일 경우 **가명처리 전후 연속형 레코드(동일 행 번호에 대응되는 속성값)** 의 피어슨 상관계수들의 평균을 절댓값 씌운 값(평균절댓값오차)를 계산합니다.
(3) “scd_sse”일 경우 가명처리 전후 연속형 벡터(속성)를 표준화하여 유클리디안(L2, 피타고라스) 거리를 계산하여 합한 값(제곱합오차)을 계산합니다.
(4) “mgd”일 경우 **가명처리 전후 범주형 레코드(동일 행 번호에 대응되는 속성값)** 간의 편집 시 문자열 조작 횟수(레벤슈타인 거리 유사도)를 계산합니다.

  </details>
<details>
      <summary>결과</summary>

(1) cosine similarity는 높을수록 유용합니다.
(2) mean correlation은 높을수록 유용합니다.
(3) standardized euclidian distance - sum of squared errors는 낮을수록 유용합니다.
(4) mean generalized difference - character attribute는 낮을수록 유용합니다.

  </details>
<details>
      <summary>시행착오</summary>

- 실행 측면
  cs, scd_sse와 달리 mc의 지표 측정 단위는 벡터 그자체가 아닌 레코드인데 이에 맞지 않는 벡터 로직을 설정하여 발생하는 타입 오류를 수정한 바 있습니다.
  지표를 측정하는 연산 과정에서 두 행렬곱(내적)을 수행하는 기능(정적 메서드)를 구현하면서 타입 오류를 겪었습니다. 단일 스칼라값을 받은 경우 단순히 곱셈값을 반환하고 판다스 시리즈 타입의 1차원 벡터를 받을 경우 같은 행 번호의 두 벡터 내 스칼라값을 곱한 후 누적합한 값을 반환하는 방식으로 문제를 해결하였습니다.
  </details>

### **4.4-1. 안전성 지표**

<details>
      <summary>제작 의도</summary>

    사용자가 비식별조치 수준을 인식할 수 있도록 가명처리 또는 익명처리 기법이 적용된 정보의 안전성에 대한 정량화된 지표를 측정합니다. 해당 지표는 이상치로 인해 중심 경향치가 왜곡되는 구간을 찾아내는 것이 목적으로, 수치가 낮을수록 안전하다고 판단합니다.
    parameter를 기준으로
    (1) “var"일 경우 자유도를 0(표본분산)이나 1(불편추정량)로 입력받아 개별 동질집합마다 연속형 민감정보의 평균 분포도를 계산합니다.
    (2) “norm_aver”일 경우 개별 동질집합의 연속형 민감정보를 정규화한 값들의 평균을 계산합니다.
    (3) “entropy”일 경우 전체 대비 각 범주형 속성값 빈도의 비율을 구하여 비균일 엔트로피를 계산하여 프라이버시 보호 모델 적결과보 손실 정도를 측정합니다.

  </details>
<details>
      <summary>결과</summary>

    (1) mean distribution equivalence class metric은 낮을수록 안전합니다.
    (2) normalized average eqivalence class size metric은 낮을수록 안전합니다.
    (3) non-uniform entropy metric은 낮을수록 안전합니다.

  </details>
<details>
    <summary>시행착오</summary>

- 구조 측면
  안전성 지표 측정 클래스는 기능 메서드의 입력받을 값을 하나로 설정하는 것이 적합하다는 것을 인식하고, 가명처리 전후 개인정보가 계산과정에서 둘다 필요한 유용성 지표 측정 클래스와 차별화하였습니다.
- 도메인 측면
  재식별 위험을 정량화하기 위한 데이터 단위를 설정함에 있어 기준을 수립하는 과정에서 모호함을 느꼈습니다.
  동질집합[준식별자 속성 조합]별 민감정보의 속성값 중 특정 개인식별가능정보 조합만 조회되도록 안전성 평가지표 측정 단위를 한정하였습니다.
  </details>

# 5. 테스트 시나리오

### **5.1. 라이브러리 활용 배경**

- 인하우스 퍼포먼스 마케터 MJ는 SH카드에서 여신금융거래데이터를 제공받아 마케팅 목적으로 활용하려고 합니다.
- 구매력 있는 이커머스 플랫폼 유저에 대한 신규 정기결제권 패키지 마케팅 PUSH 수신 대상자 선정을 하기 위해 다음과 같은 조치가 필요합니다.
- MJ가 재직 중인 회사의 고객 데이터 중 특정인을 식별할 수 있는(재식별) 가능성이 높은 정보를 선별하여 가명처리 후 내부결합을 수행합니다.
- 다만, 가명정보 취급자(가명정보에 대한 접근권한을 가지고 이를 이용 및 제공하는 처리자)인 마케터 MJ는 직접 비식별조치를 할 수 없습니다.
- 따라서 MJ는 사내 개인정보 보호 담당자인 SY에게 원본정보 가명처리와 내부결합과 개인정보 보호위원회(KISA 위탁)에 대한 가명처리 적정성 검토를 의뢰합니다.

### 5.2. 가명처리 대상 선정

- 먼저 SY는 SH카드에서 제3자 제공받은 여신금융거래 데이터인 FINANACE_CONSUMER DB(Schema)에 정의된 DATA_FINANCE 테이블(엔터티) 내 INCOME_BRACKET(소득분위), CREDIT_SCORE(신용평점), AMT_CREDITCARD_PAYMENT(신용카드 결제금액), AMT_CREDITLOAN(신용대출 실행금액) 속성(컬럼)값에 가명처리기법을 적용합니다.
- 다음으로 SY는 MJ가 데이터 분석하고자 하는 회사 고객의 구매데이터인 같은 스키마에 정의된 DATA_RETAIL 테이블(엔터티) 중 AMT_PURCHASES_BOOKS, AMT_PURCHASES_CULTURE, AMT_PURCHASES_EDU 속성(컬럼)에 가명처리기법과 프라이버시 보호기법을 적용합니다.

### 5.3. 가명처리∙결합∙반출 라이브러리 적용

- SY는 테이블에서 고객을 분류할 수 있는 컬럼을 조합하여 가지고 동질집합을 만든 후 이를 기반으로 (1)가명처리기법 모듈과 (2)프라이버시 보호모델 모듈을 적용합니다.
  결과 대한 이후 (3)성과지표 모듈을 활용하여 가명정보의 유용성과 안전성 지표를 측정합니다.
- 가명처리결과데이터프레임으로 생성하여 DB 내 저장합니다.
- (4)결합 모듈로 두 가명정보의 결합을 수행한 후 내부조인된 데이터 + 병합되지 않은 데이터를 DB 내 저장합니다. (단, 병합할 때 가명결합 여부를 식별할 수 있도록 처리합니다.)

# 6. 한계와 의의

### 6.1. 한계

- 가명처리 모듈을 활용할 때 개별 가명처리 기법을 적용하기 위한 객체 설정 시 입력값이 많아 사용자가 이용하기에 복잡하게 느껴질 수 있습니다.
- 시간의 한계로 인해 암호화 기법을 다양하고 깊이 있게 적용하지 못했습니다.
  - 해시 알고리즘 SHA256, SHA512 이외의 타 알고리즘을 C언어 등 다른 방식으로 구현해야 합니다.
  - 암호키 보안 성과지표를 추후 만들어 보완할 필요가 있습니다.
- 스키마와 테이블명을 모두 입력해야 하는 특성상 클래스, 메서드별 입력값이 많아지면서 사용하기 복잡해졌습니다.

### 6.2. 의의

- 가명처리∙가명정보 결합에 관한 논문이나 학술지 외 프로그래밍 레퍼런스가 부족한 상황에서 법률적∙행정적 절차를 기술적으로 구현하는 작업
- 상용 비식별조치 솔루션과 달리 사용자가 이상치를 탐색하고 처리하는 통계적 기법을 세분화
- 솔루션과 달리 사용자 편의를 고려하여 DB 스키마 테이블을 SQL 쿼리문을 통해 직접 조작하여 가명정보 결합을 수행

# **7. 참고문헌**

- 「가명정보 처리 가이드라인」, 개인정보 보호 위원회 - 한국인터넷진흥원, 2022.04.
- 「가명정보 활용 전문인재를 위한 온라인 교육과정 - 실무자」, 한국인터넷진흥원, 2023.10.
- 이창엽,「가명/익명처리 기술과 적정성 검토」, SK텔레콤 정보보호팀 · DEVOCEAN.
- 김순석,「제6회 가명 · 익명처리 기술 경진대회 준비를 위한 사전교육」, 과학기술정보통신부 · 한국인터넷진흥원 **·** (주)컬쳐메이커스 , 2023.10.
- 고학수 외 3인,「개정 개인정보 보호법상 가명정보의 개념 및 가명처리에 관하여」, 서울대학교 인공지능정책 이니셔티브 이슈페이퍼 2020-1, 2020.01.
- 김승환,「민감정보 비식별화(Sensitive Data De-identification)」, 인하대학교 · 한국정보화진흥원.
- 「데이터 분석에 기본이 되는 경험분포 」, 성균관대학교 수학과 보험계리학 전공 강의자료.
- 고학수 외 1인,「데이터 3법 시대의 과제: 가명처리, 연구목적 활용, 데이터 거래 웨비나 - 차분 프라이버시(DifferentialPrivacy)의 가능성과 한계」, 서울대학교 인공지능정책 이니셔티브 이슈페이퍼 2020-01, 2020.05.
- 박성률 외 2인,「최신 차분 정보보호 방법론 탐색과 통계 활용 방안 연구」, 통계청 통계개발원 연구보고서 2022-06(발간등록번호 11-1240245-000057-14), 2023.04.
- 이혁기 · 심규석 외,「학계 전문가 초청, 개인정보 비식별 세미나」, 한국인터넷진흥원 · 서울대학교 · 고려대학교, 2017.06.
- 정수용 외,「로컬 차분 프라이버시 실제 적용 사례연구 : 프라이버시 보존형 설문조사」, Journal of The Korea Institute of Information Security & Cryptology VOL.30, NO.1, 2020.02.
- 박성률 외 1인, 「데이터 유용성이 개선된 Differential Privacy 방법들에 대한 사례 검증 연구」, 통계청 통계개발원 통계연구 제28권 제4호, 2023.11.
- 강인규,「쉽고 재밌는 정규식 이야기」, [우아한 기술블로그](https://techblog.woowahan.com/2505/), 2016.08.
- 김현태 외 1인, 「데이터 가명 익명처리 기법의 현황과 대안: 재현데이터를 중심으로」, 보험연구원, 2023.07.
- 전승재 외 1인, 「일방향 암호화와 개인정보 비식별화의 관계 - 약학정보원 민사 1심 판결에 대한 비판적 검토」, 고려대학교 정보보호대학원
- 브렛 슬라킨, 「파이썬 코딩의 기술」, 길벗, 2020.10.
- 개인정보 보호법 제28조의3(신용정보법 제17조의2)에 의한 가명정보의 결합 및 반출 등에 관한 고시[개인정보 보호위원회 고시 제2024-2호, 2024.01.30., 일부개정]
- 한국인터넷진흥원, 「암호 알고리즘 및 키 길이 이용 안내서」, 2018.12.
- 한국인터넷진흥원, 「암호 키 관리 안내서」, 2014.12