4 minute read

프로젝트 진행 기간

  • 2021-07-06(화) ~ 2021-07-12(월)

데이터 분석 목적

  • 원하는 기업과 복지혜택이 유사한 기업을 찾음으로 지원의 폭을 넓혀주는 역할을 기대
  • 멋쟁이 사자처럼 강의 내 팀 프로젝트 진행 내용

데이터 출처

  • https://data101.oopy.io/startup-benefits
  • 로켓펀치 크롤링을 통해 100억 이상 스타트업을 조회 후 채용공고가 올라 온 기업에 한하여 사내 복지 항목 수집

사용 라이브러리

  • Numpy
  • Pandas
  • Sklearn
  • Seaborn
  • Matplotlib

분석 프로세스

  • 데이터 전처리
  • 사용자가 원하는 기업과 복지혜택의 우선순위 입력
  • 입력 내용을 토대로 유사 기업 분석 진행
  • 가중치 평균에 대한 시각화 진행
  • 복지 혜택이 유사한 상위 10개 社 출력

데이터 확인 및 전처리 과정

  • 마지막 3개열 삭제
  • 출/퇴근 시간이 있는 경우, 정시근무로 인식하고 시간 삭제
  • NaN의 경우, Vectorizer 진행 시 자동으로 삭제되어 Cosine Similarity 진행 시 계산이 진행되지 않음
  • NaN는 ‘Empty’로 변경
df = pd.read_excel("로켓펀치 100억 이상 투자유치 스타트업 사내 복지 (labeling ver.) by 김문과의 데이터.xlsx", encoding='utf-8')
df.head(3)

#불필요한 열 제거
df.drop(['Unnamed: 8','Unnamed: 9','Unnamed: 10'], axis=1, inplace=True)

# 띄어쓰기나 특수기호를 넣을경우, countervectorizer가 자동으로 삭제 
# -> 비교대상이 없어서져 코사인유사도 계산 시 들어가는 vector값이 NaN 나옴. 
# 문자열 empty로 채워줌. 
df.fillna(value="Empty", inplace=True)

# string을 replace할 때는 to_replace(from) & value(to) 
# 문자열 replace는 regular expression(정규표현식) regex=True 설정.
df.replace(to_replace='/', value='', regex=True, inplace=True)

# 근무형태 데이터 전처리
df['근무형태'] = df['근무형태'].str.replace('[0-9]', '')
df['근무형태'] = df['근무형태'].str.replace(':-:,', '')
df['근무형태'] = df['근무형태'].str.replace(':-:', 'Empty')
df['연차휴가'] = df['연차휴가'].str.replace(' ', '')

image

사용자의 원하는 기업 및 복지혜택의 우선순위 입력

  • 기준이 되는 원하는 회사의 경우 영문으로 입력
  • 띄어쓰기를 해도 문제가 없도록 코드 작성
  • 우선순위에 대해 번호로 입력하고 전 순위와 중복되거나 선택지 밖으로 벗어날 시 재입력하도록 설계

image

입력 내용을 토대로 유사 기업 분석 진행

  • 입력받은 기업 기준으로 Cosine similarity 진행
  • CountVectorizer을 이용하여 분석 진행
  • TF-IDF를 사용하지 않은 이유는 키워드만 들어있었기 때문
  • 복지혜택의 우선순위에 따라 가중평균법 이용하여 총점 도출
  • 1순위는 0.5, 2순위는 0.3, 3순위는 0.2의 가중평균을 이용하여 총점 1점에 맞춰 설계

image

가중치 평균에 대한 시각화 진행

  • 항목별, 총점별 시각화 진행
  • 하기 그림과 같이 기준이 되는 기업 포함 총점 기준 상위 10개 社 시각화 진행
  • 0-1은 일치 수준에 대해 나타냄 (1에 가까울수록 조건 일치)
  • APRIL 社 기준 Peoplefund, Madit 순으로 사용자가 선택한 우선순위에 따른 복지혜택이 유사함

image image image

복지 혜택이 유사한 상위 10개 社 출력

  • 위 결과를 토대로 기준 기업 포함 복지혜택이 유사한 상위 10개 社

image

코드 특이사항

  • 유저가 각기 다른 방법으로 회사입력을 할 경우를 고려함
    • 띄어쓰기 할 경우 해당 내용을 띄어쓰기 없이 받아들이는 방법을 채택
    • 대문자 입력 시, 소문자로 받아들이는 방법 채택
# 유저 인풋받기

# 회사명 input 받는 코드
while True:
    company = input('회사명을 영문으로 입력해주세요(종료 : q) : ').lower().replace(" ","")
    # 유저가 실수로 대문자 or 띄어쓰기를 추가한 경우에도 문제없이 인풋으로 받을 수 있도록 : lower, replace
    if company == 'q': # q를 입력했을 때 루프는 빠져나갈 수 있도록 
        company = '다시 실행해주세요.'
        break
    if company not in df['회사명'].values: # 데이터셋에 없는 회사를 입력하면 오류 프린트
        print('해당 회사에 대한 정보가 없습니다.')
        continue
    break
print(company)

  • 우선순위의 경우, 번호로 입력하게 진행하였으며 오류 및 중복 시 재입력 진행하는 방법 채택
print('🍕 [우선 순위 선택 항목: (1:개인장비, 2:통근교통, 3:자기계발, 4:식사간식, 5:근무형태, 6:보험의료, 7:연차휴가)] / 종료 : q 🍔')

dict_category = {
    '1':'개인장비', 
    '2':'통근교통', 
    '3':'자기계발', 
    '4':'식사간식', 
    '5':'근무형태', 
    '6':'보험의료', 
    '7':'연차휴가'
}
keys = list(dict_category.keys())
while True:
    first = input('1순위 선택 항목을 입력해주세요 : ' )
    if first == 'q':
        first = '실행이 종료됩니다. 다시 실행해주세요.'
        break
    if first not in keys:
        print('선택 항목을 벗어났습니다.') # 1-7을 벗어난 index 입력시 오류 프린트
        continue
    break
while True:
    second = input('2순위 선택 항목을 입력해주세요 : ' )
    if second == 'q':
        second = '실행이 종료됩니다. 다시 실행해주세요.'
        break
    if second not in keys:
        print('선택 항목을 벗어났습니다.')
        continue
    elif second == first:
        print('1순위 항목과 중복됩니다.') # 인풋이 중복되었을 때도 오류를 프린트
        continue
    break
while True:
    third = input('3순위 선택 항목을 입력해주세요 : ' )
    if third == 'q':
        third = '실행이 종료됩니다. 다시 실행해주세요.'
        break
    if third not in keys:
        print('선택 항목을 벗어났습니다.')
        continue
    elif third == first:
        print('1순위 항목과 중복됩니다.')
        continue
    elif third == second:
        print('2순위 항목과 중복됩니다.')
        continue
    break
    
print('\n','회사명:',company,'\n','선택된 복지혜택:', '1️⃣',first,'2️⃣', second,'3️⃣', third)
  • sklearn 라이브러리 내의 코사인 유사도 함수의 경우 DataFrame으로 받아서 연산을 진행함.
  • 하기 2가지 문제로 인하여 sklearn 내부 함수를 이용하지 않고 함수를 구현하여 사용
    • array 형식으로 연산을 진행
    • 분모가 0일 경우 RuntimeWarning: invalid value encountered in true_divide 오류 발생
  • 기준 회사를 먼저 정하고 우선순위 순서로 CountVectorizer을 진행한 array로 연산을 진행함
  • 분모가 0일 경우를 대비하여 Seterr 함수를 사용하여 제어함
  • 최종 출력 DataFrame으로는 기준 회사에 대한 유사 회사 순위로 sort하여 도출

import warnings
warnings.filterwarnings("ignore") 

weights = {0:0.5, 1:0.3, 2:0.2} 

df_temp = df.copy()
df_final = df_temp[['회사명']]
for index, inPut in enumerate(inputs): # inputs내 3가지 복지혜택 열 모두에 적용
    # 차후 enumerate(inputs): {0:1순위복지,1:2순위복지,2:3순위복지}와 대응
    corpus = []
    for value in df[inPut]:
        corpus.append(value)
    vector = CountVectorizer()
    arr_column = vector.fit_transform(corpus).toarray() #코퍼스(inPut의 value)로부터 각 단어의 빈도수를 기록
    print(vector.vocabulary_) 
    
    #cosine similarity
    #유저가 선택한 기업의 index를 기준으로, 나머지 docs(회사)들의 상대적인 유사도 도출
    #그러려면 선택한 회사의 index 번호를 알아야 한다. 그 회사의 행렬을 뽑기 위해서는
    picked_index = df.index.values[df['회사명']==company][0]
    
    #코사인 유사도 계산함수. !=cosine_similarity 공식
    def cos_sim(A,B): 
        # 분모가 0일 경우: RuntimeWarning: invalid value encountered in true_divide <- 해당 에러 발생...
        np.seterr(divide = 'ignore', invalid = 'ignore')
        return dot(A, B)/(norm(A)*norm(B))
    
    sim_list = []
    
    doc1 = arr_column[picked_index] # 기준열
    
    for doc2 in arr_column : #상대열  
        sim_list.append(cos_sim(doc1,doc2)) 
        
    df_temp[inPut] = sim_list 
    # df_temp : (유저가 입력한 기준 열의) value값을 코사인 유사도 값으로 대체
    df_final[inPut] = [x*weights[index] for x in sim_list] 
    # 만들어놓은 데이터프레임에 위의 리스트에서 코사인유사도 값을 하나씩 받아 입력
    df_temp.head()
        
df_temp
        
df_final['Total'] = df_final[inputs].sum(axis=1)  

df_final= df_final.sort_values(by='Total', ascending=False).head(10)
df_final.head()

# 코사인 유사도 계산 함수. cosine_similarity는 df를 인풋으로 받으나, 이 함수는 numpy array를 input으로 받음.

image

진행 시 고려 및 어려웠던 점

  • 유저 입력 시 최대한 오류 없이 Input 값을 받을 수 있도록 고안함
  • 기업 간 Cosine similarity 진행할 때에 도출 방법에 대해 2가지 안을 고민함
    • 1안 : 각 기업별 모든 Cosine similarity를 미리 구하고 DataFrame에서 불러오는 방법
    • 2안 : 기준 회사 입력 시 그 때마다 계산하여 출력하는 방법
  • 위 2가지를 고민하였을 때에 2안이 더 나을 것이라고 판단하여 2안으로 진행함