복지 혜택 유사도에 따른 회사 추천 시스템
프로젝트 진행 기간
- 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(' ', '')
사용자의 원하는 기업 및 복지혜택의 우선순위 입력
- 기준이 되는 원하는 회사의 경우 영문으로 입력
- 띄어쓰기를 해도 문제가 없도록 코드 작성
- 우선순위에 대해 번호로 입력하고 전 순위와 중복되거나 선택지 밖으로 벗어날 시 재입력하도록 설계
입력 내용을 토대로 유사 기업 분석 진행
- 입력받은 기업 기준으로 Cosine similarity 진행
- CountVectorizer을 이용하여 분석 진행
- TF-IDF를 사용하지 않은 이유는 키워드만 들어있었기 때문
- 복지혜택의 우선순위에 따라 가중평균법 이용하여 총점 도출
- 1순위는 0.5, 2순위는 0.3, 3순위는 0.2의 가중평균을 이용하여 총점 1점에 맞춰 설계
가중치 평균에 대한 시각화 진행
- 항목별, 총점별 시각화 진행
- 하기 그림과 같이 기준이 되는 기업 포함 총점 기준 상위 10개 社 시각화 진행
- 0-1은 일치 수준에 대해 나타냄 (1에 가까울수록 조건 일치)
- APRIL 社 기준 Peoplefund, Madit 순으로 사용자가 선택한 우선순위에 따른 복지혜택이 유사함
복지 혜택이 유사한 상위 10개 社 출력
- 위 결과를 토대로 기준 기업 포함 복지혜택이 유사한 상위 10개 社
코드 특이사항
- 유저가 각기 다른 방법으로 회사입력을 할 경우를 고려함
- 띄어쓰기 할 경우 해당 내용을 띄어쓰기 없이 받아들이는 방법을 채택
- 대문자 입력 시, 소문자로 받아들이는 방법 채택
# 유저 인풋받기
# 회사명 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으로 받음.
진행 시 고려 및 어려웠던 점
- 유저 입력 시 최대한 오류 없이 Input 값을 받을 수 있도록 고안함
- 기업 간 Cosine similarity 진행할 때에 도출 방법에 대해 2가지 안을 고민함
- 1안 : 각 기업별 모든 Cosine similarity를 미리 구하고 DataFrame에서 불러오는 방법
- 2안 : 기준 회사 입력 시 그 때마다 계산하여 출력하는 방법
- 위 2가지를 고민하였을 때에 2안이 더 나을 것이라고 판단하여 2안으로 진행함