[EDA test] 02. 화장품 성분 데이터

svenskpotatis·2023년 10월 11일
0

  • 1단계 DataFrame 불러오기 & 기초 전처리
  • 2단계 전처리
  • 3단계 Mapping

데이터 원본 출처

Target Data(CSV): 화장품의 종류, 브랜드, 제품명, 성분명 등이 포함된 csv 파일

성분 사전: 화장품 전성분의 한글명, 표준영문명, 구명칭, 구 영문명이 포함된 pdf 파일

참고사항

  • pdf를 Pandas DataFrame으로 변환하기 위해 Tabula 등의 Library를 사용할 수 있음.
  • 혹, 설치시 오류가 발생하거나 코드 실행에 어려움을 겪을 경우 해당 pdf를 읽어 만든 pickle 파일 제공(ingredients_list.pkl)
  • 해당 pickle파일은 List형태로 되어 있으며, List 내의 Elements는 각 페이지별 표가 dataframe 형태로 되어 있음
    • ex: [df1, df2, df3, ...]

작업절차

  • 파이썬으로 PDF 파일을 읽은 후, pickle 이라는 포맷의 데이터로 저장합니다.
    • 파이썬으로 PDF 파일을 읽기 위해 필요한 모듈
      • pip install tabula-py
    • pickle 파일이란?
      • pickle은 파이썬에서 사용하는 딕셔너리, 리스트, 클래스 등의 자료형을 변환 없이 그대로 파일로 저장하고 이를 불러올 때 사용하는 모듈
      • 설명출처: https://wikidocs.net/110788
import tabula
import pickle

# Tabula로 PDF 읽기 -> DataFrame List
ingredients_list = tabula.read_pdf('./datas/별첨1. 표준화명칭목록_220530.pdf', pages='all', lattice=True)

# DataFrame List를 Pickle로 저장
with open('./datas/ingredients_list.pkl', 'wb') as f:
   pickle.dump(ingredients_list, f)

1단계: 성분사전 불러오기 & Dataframe 합치기, 수정하기

# Pickle File 불러오기
import pickle

with open('./datas/ingredients_list.pkl', 'rb') as f:
    ingredients_list_pkl = pickle.load(f)

문제 1-1) 성분사전 DataFrame 만들기

  • Pickle을 이용해 Load한 ingredients_list_pkl를 하나의 DataFrame으로 합치세요.
    • 조건1: 데이터는 index 기준으로 합치세요.
    • 조건2: join없이 단순히 합치세요.
import pandas as pd

ingredients_df = pd.concat(ingredients_list_pkl, ignore_index=False)
ingredients_df

문제 1-2) 성분사전 DataFrame 내의 Data 수정하기 - 1(15점)

  • 이 성분 사전 DataFrame에는 원본에는 없는 '\r' 이 아래와 같이 data 사이에 끼어 있습니다. 이 '\r'을 삭제하세요
  • hint1: '\r'를 대체 할 때 한글('표준 성분명', '구명칭')의 경우 띄어쓰기가 없고, 영어('표준 영문명', '구영문명)의 경우 띄어쓰기를 해야 합니다.
  • hint2: 모든 영문명의 경우 대문자로 시작합니다.
import re

# \r 바꾸는 함수
def changeR(text, lang):
    text = str(text)
    
    if lang == 'kor':
        text = text.replace('\r', '')
        return text
    
    elif lang == 'eng':
        pattern = r'\r[A-Z]'          # \r대문자 형태는 띄어쓰기로 바꿈

        match = re.search(pattern, text)
        if match: 
            text = text.replace('\r', ' ')

        text=text.replace('\r', '')

        return text

'\r'뒤에 대문자가 오는 형태는 공백을 넣어야 하고, 뒤에 한글이나 특수문자(괄호)가 오는 형태는 아예 지워야 한다. 특수문자가 오는 형태를 힌트에서 명시하지 않아서 헷갈렸는데, output을 보면서 유추해서 조건을 찾았다.

  • \r 가 어떤 형태로 들어가 있는지 확인하기
check = kor[kor['표준 성분명'].str.contains('\r')]
check

# 결과 통해 \r 뒤에 괄호가 오는 형태 확인함 
check = olEng[olEng['구영문명'].str.contains('\r')]  
#check = pd.set_option('max_colwidth', 1000)
  • 바꾸기
# 구영문명 바꾸기
olEng['구영문명'] = [changeR(value, 'eng') for value in olEng['구영문명']]
# 바뀐 것 확인
result = olEng['성분코드'] == 230
olEng[result]

result = olEng['성분코드'] == 3424
olEng[result]
# 나머지 바꾸기
stEng['표준 영문명'] = [changeR(value, 'eng') for value in stEng['표준 영문명']]
ingredients_df['표준 성분명'] = [changeR(value, 'kor') for value in ingredients_df['표준 성분명']]
ingredients_df['구명칭'] = [changeR(value, 'kor') for value in ingredients_df['구명칭']]
del ingredients_df['표준 영문명'] 
del ingredients_df['구영문명'] 

ingredients_df = pd.merge(ingredients_df, olEng, how='outer', on='성분코드')
ingredients_df = pd.merge(ingredients_df, stEng, how='outer', on='성분코드')

order = ['성분코드', '표준 성분명', '표준 영문명', '구명칭', '구영문명']
ingredients_df = ingredients_df[order]

문제 1-3) 성분사전 DataFrame 내의 Data 수정하기 - 2

  • pdf를 dataframe으로 전환하면서 일부 누락된 데이터가 있습니다. 아래 cell의 replace_dict는 현재값(key):변경할값(value)의 쌍으로 이루어져 있습니다. 이 replace_dict를 이용하여 성분사전 dataframe '표준 영문명' column의 값을 변경하세요.
replace_dict = {...}
ingredients_df['표준 영문명'] = ingredients_df['표준 영문명'].replace(replace_dict)

2단계: Target Data 수정하기

문제 2-1) Target DataFrame 중 Ingredients Column 내의 Data 수정하기

  • Kaggle에서 가져오는 데이터는 수정을 필요로 하는 경우가 있습니다. 제시된 Target DataFrame 또한 성분사전과 다른 표현을 쓰는 경우가 있어 해당 내용에 대해 수정을 하고자 합니다. 다음 조건에 맞게 Ingredients Column의 데이터를 수정하세요.
    • 조건1: 맨 끝에 마침표('.')가 있다면 마지막 마침표만 제거하세요
      • ex) 'Algae (Seaweed) Extract. Sea Salt.' -> 'Algae (Seaweed) Extract. Sea Salt'
    • 조건2: '. May Contain'를 포함하고 있다면, '. May Contain' 이후의 데이터를 제거하세요
      • ex) 'Algae (Seaweed) Extract. May Contain: Sea Salt, Fragrance' -> 'Algae (Seaweed) Extract'
    • 조건3: 아래의 replace_str_dict는 현재값(key):변경할값(value)의 쌍으로 이루어져 있습니다. 이 replace_str_dict를 이용하여 데이터를 변경하세요
import re
# 수정 함수
def changeTar(text):
    
    # condition 1
    pattern1 = r'[.]$'
    match1 = re.search(pattern1, text)
    if match1:
        text = text[:-1]

    # condition 2
    pattern2 = r'[.] May Contain.'
    match2 = re.search(pattern2, text)
    if match2:
        n = match2.start()
        text = text[:n]

    return text
df_target['Ingredients'] = [changeTar(value) for value in df_target['Ingredients']]
# contidion 3
replace_str_dict = {...}

for old, new in replace_str_dict.items():
    for i, ingredient in enumerate(df_target['Ingredients']):
        df_target.loc[i, 'Ingredients'] = ingredient.replace(old, new)

문제 2-2) Target DataFrame 중 'Ingredients' Column Data 변환하기(10점)

  • 다음 조건에 맞게 앞서 수정한 'Ingredients' Column의 데이터를 변환하여 'Ingredients List' Column에 입력하세요.
    • 조건1: 'Ingredients' Column의 각 데이터를 ', '(쉼표+띄어쓰기)로 분리하여 List로 변환하세요
      • ex) ' Algae (Seaweed) Extract, Sea Salt ' -> [' Algae (Seaweed) Extract', ' Sea Salt ']
    • 조건2: 조건1에서 변경한 list의 각 Element 앞뒤의 공백이 있다면 공백을 삭제하세요
      • ex) [' Algae (Seaweed) Extract', ' Sea Salt '] -> ['Algae (Seaweed) Extract', 'Sea Salt']
    • 조건3: 'Ingredients List' Column을 새로 생성하여 조건1과 조건2에서 만든 list를 각 행에 맞게 입력하세요
  • Ingredients column 만 target 에 저장
target = pd.DataFrame()
target['Ingredients'] = df_target[['Ingredients']]
import re

# \r 바꾸는 함수
def changeR(text, lang):
    text = str(text)
    
    if lang == 'kor':
        text = text.replace('\r', '')
        return text
    
    elif lang == 'eng':
        pattern = r'\r[A-Z]'          # \r대문자 형태는 띄어쓰기로 바꿈

        match = re.search(pattern, text)
        if match: 
            text = text.replace('\r', ' ')

        text=text.replace('\r', '')

        return text
def bracket(df, column):
    inBracket = [[] for _ in range(len(df))]

    for idx, _ in enumerate(inBracket):
            inBracket[idx].append(df[column][idx])

    inBracket = [[item] for item in inBracket]
    return inBracket
targetLists = bracket(target, 'Ingredients')

ingredients_list = pd.DataFrame(targetLists, columns=['Ingredients List'])

df_target['Ingredients List'] = ingredients_list['Ingredients List']

3단계: 성분사전(Ingredients Dictionary)을 이용하여 Mapping하기

문제 3-1) Target DataFrame 의 'Ingredients List' Column를 Mapping하여 'Code List' Column 만들기(15점)

  • (아래 예시를 참고)성분사전(Ingredients Dictionary)를 이용하여 Target DataFrame의 'Ingredients List'를 각 성분에 Mapping되는 'Code List'로 만들고, 'Code List' Column을 만들어 Code List를 각 행에 맞게 입력하세요.

  • 조건1: Ingredients List에 대응하는 Code List의 순서는 같아야 합니다.

    • hint1: '표준 영문명'에서 찾지 못한다면 '구영문명'으로도 찾아보세요

    • hint2: 대문자-소문자 차이가 있을 수 있습니다.

# 표준 영문명 & 성분코드
stEngDict = ingredients_df.set_index('성분코드')['표준 영문명'].dropna().to_dict()

# 구영문명 & 성분코드
olEngDict = ingredients_df.set_index('성분코드')['구영문명'].dropna().to_dict()

# 모두 대문자로 바꾸기
def toUpper(dict):
    return {key: value.upper() for key, value in dict.items()}
pd.set_option('max_colwidth', 10000)
df_target[['Ingredients List']]

olEngDict = toUpper(olEngDict)
stEngDict = toUpper(stEngDict)

splPattern = r'(?<!1,2-), \s*'

for iList in ingredients_list['Ingredients List']:
    seperate = [item.strip() for item in re.split(splPattern, iList[0])]
    iList[0] = seperate
# 모두 대문자로 바꾸기 - 리스트
def toUpperList(li):
    return [item.upper() for item in li]
lists = []

for iList in ingredients_list['Ingredients List']:

    upperSep = toUpperList(iList[0])
    lists.append(upperSep)
    print(lists)

    # 표준 영문명에 있음
    for code, name in stEngDict.items():
        if name in upperSep:
            index = upperSep.index(name)
            upperSep[index] = code

    # 구영문명에 있음
    for code, name in olEngDict.items():
        if name in upperSep:
            index = upperSep.index(name)
            upperSep[index] = code
    print('-'*20)
for inlist in lists:
    print(inlist)
  • 또 시간을 많이 잡아먹었던 부분 --> 1, 2-hexanediol 은 split 하면 안된다! 코드 수정하기~
# split 했던 것 다시 join
def joinList(spList):
    return ', '.join(spList[0])

ingredients_list['Ingredients List'] = ingredients_list['Ingredients List'].apply(joinList)
  • Ingredients List 을 원래 형태로
# 원래 형태로
a = bracket(ingredients_list, 'Ingredients List')
a = pd.DataFrame(a, columns=['Ingredients List'])
df_target['Ingredients List'] = a['Ingredients List']
  • code list 추가
for inlist in codelist:
    print(inlist)

df_target['Code List'] = [codelist[0], codelist[1], codelist[2], codelist[3], codelist[4]]
df_target

문제 3-2) 다음 조건을 만족하는 code들을 찾아 그 code들에 해당하는 DataFrame을 구하세요(15점)

  • Target DataFrame의 Code List를 각 행 내에서 중복 없이 모두 합쳐 두 번 나온 수를 오름차순으로 정렬하고, 첫번째부터 다섯번째까지의 수들을 찾아 성분사전(Ingredients Dictionary)를 이용하여 해당 Code들의 DataFrame을 구하세요.
import copy
codelist3 = copy.deepcopy(codelist)

for codes in codelist3:

    # 오름차순
    codes.sort()

    # 중복 제거
    codes = list(set(codes))
  • code number 를 딕셔너리의 key 로 만들어서 중복값 세기
codeDict = {key: 0 for key in codelist3[0]}
print(codeDict)

>>> {9: 0, 20: 0, 37: 0, 46: 0, 47: 0, 86: 0 ...
for key, value in codeDict.items():
    for codes in codelist3:
        for code in codes:
            if code == key:
                codeDict[key] += 1

print(codeDict)

>>> {9: 2, 20: 2, 37: 2, 46: 2, 47: 1, 86: 1, 317: 1, ...
  • 두 번 나온 값
finalDict = {key: value for key, value in codeDict.items() if value == 2}
  • 첫번쨰부터 다섯번째
finalCodes = []
for key in finalDict.keys():
    finalCodes.append(key)

print(finalCodes)
finalCodes = finalCodes[:5]
print(finalCodes)
final_df = pd.DataFrame()
for code in finalCodes:
    new_row = ingredients_df['성분코드'] == code
    final_df = pd.concat([final_df, ingredients_df.loc[new_row]])

0개의 댓글