코딩복습장

영화 관객 수 데이터분석 본문

데이터 분석

영화 관객 수 데이터분석

코복장 2023. 6. 23. 20:11
728x90

오늘은 kobis의 2015~2023년 영화 데이터를 분석해보려고 한다.

column의 개수는 총 19개이고 데이터의 개수는 14301개이다.

 

Data preprocessing

 

missing value에 대한 graph를 뽑아보았다. (데이터의 분포 확인)

다음과 같이 나오는 것을 확인할 수 있는데 여기서 알 수 있는 점은 영화의 제작사와 수입사 column에 null데이터가 굉장히 많다는 것 이었다. 따라서 제작사와 수입사 column을 제거시켜주었고 영화 유형과 영화 형태 column의 경우 동일한 데이터가 굉장히 많아 제거시켜주었다.

 

 

 영화 등급 column preprocessing

 

영화 등급의 개수 그래프를 출력해보았다.  여기서 청소년관람불가, 15세이상관람가, 12세이상관람가, 전체관람가를 

제외한 나머지 데이터는 거의 없는 것을 확인할 수 있다. 따라서 이 4가지 데이터를 제외한 나머지 데이터를 모두 제거해주었다.

 

영화국적 column preprocessing

 

이 그래프를 봤을 때 미국부터 영국까지 상위 6개의 나라를 제외한 나머지 국가의 매출액 평균은 거의 비슷한 것을 알 수 있다. 따라서 이 6개의 국가를 제외한 나머지국가는 기타로 처리해주었다. 

 

다음은 영화감독 column에 대한 preprocessing과정이다.

나는 여기서 영화감독과 target데이터인 전국매출액간의 상관관계를 가지게 만들고 싶었다.

고민을 하다 상위 15명의 평균매출액이 높은 감독과 영화를 많이 많든 상위 15명의 감독을 순위를 매기는 rank encoding을

적용하기로 결정했다 

ex) 이상용 감독 -> 0, 카무라 히로유키 감독-> 29

 

이렇게 될 경우 예상되는 상관관계는 음의 값이다. (감독과 총 매출액간의 상관관계가 반비례하도록 만들었기 때문)

 

다음은 영화 배급사 column의 preprocessing과정이다.

영화 배급사도 영화감독과 동일한 code를 사용했다. 

 

df_distributor_list = df.groupby(['배급사'])['전국매출액', '서울매출액'].mean().sort_values(by=['전국매출액'], ascending=[False])
df_distributor_list = df_distributor_list[:15].index + df['배급사'].value_counts()[:15].index
# 가장 영화를 많이 만든 상위 15개의 제작사와 매출액이 가장 높은 상위 15개의 배급사 목록을 만들었다.
def get_distributor(x): # 매출액 상위 15명과 영화를 많이 만든 상위 15명 배급사를 제외하고 나머지를 기타로 바꿔주는 함수
  df_distributor_filter = list(df.groupby(['배급사'])['전국매출액', '서울매출액'].mean().sort_values(by=['전국매출액'], ascending=[False])[:15].index) + list(df['배급사'].value_counts()[:15].index)
  for filter in df_distributor_filter:
    if (df_distributor_filter.count(x)>0):
      return x
    else:
      return '기타'

df['배급사'] = df['배급사'].apply(get_distributor)# 함수 적용

 

이 부분이 살짝 복잡해서 따로 설명하겠습니다.

df.groupby(['배급사'])['전국매출액', '서울매출액'].mean().sort_values(by=['전국매출액'], ascending=[False])

코드를 차례대로 살펴보자 

 

df.groupby(['배급사'])['전국매출액', '서울매출액'].mean()

 

배급사를 groupby를 통해 묶은 후 전국매출액과 서울매출액의 평균을 데이터로 추가해서 index를 배급사로가지고

전국매출액과 서울매출액의 평균을 column으로가진 데이터프레임이 생성된다.

 

df.groupby(['배급사'])['전국매출액', '서울매출액'].mean().sort_values(by=['전국매출액'], ascending=[False])

 

그 후 sort_values를 통해 전국매출액을 내림차순 정렬한다. 이렇게 되면 전국매출액의 평균이 내림차순 정렬됨에 따라 index인 배급사도 내림차순 정렬된다. 이렇게 되었을 때 우리가 구하고자하는 상위 15개의 평균매출액이 높은 배급사는해당 데이터프레임의 index를 15개까지 슬라이싱한 값이 된다.

 

<아래는 전체코드이다.>

df['등급'].value_counts() # 15세관람가 부터는 개수가 너무 적기 때문에 나머지를 삭제해준다.
filters = list(df['등급'].value_counts().index[4:])

for filter in filters:
  df.drop(df[df['등급']==filter].index, axis=0, inplace=True)

df['등급'].value_counts()# 등급의 unique 값이 4개로 줄어든 것을 알 수 있다
def get_dir(x): # 매출액 상위 15명과 영화를 많이 만든 상위 15명 감독을 제외하고 나머지를 기타로 바꿔주는 함수
  director_filter = list(df.groupby(['감독'])['전국매출액', '서울매출액'].mean().sort_values(by=['전국매출액'], ascending=[False]).index[:15]) + list(df['감독'].value_counts().index[:15])
  for filter in director_filter:
    if (director_filter.count(x)>0):
      return x
    else:
      return '기타'

df['감독'] = df['감독'].apply(get_dir)# 함수 적용

df_distributor_list = df.groupby(['배급사'])['전국매출액', '서울매출액'].mean().sort_values(by=['전국매출액'], ascending=[False])
df_distributor_list = df_distributor_list[:15].index + df['배급사'].value_counts()[:15].index
# 가장 영화를 많이 만든 상위 15개의 제작사와 매출액이 가장 높은 상위 15개의 배급사 목록을 만들었다.
def get_distributor(x): # 매출액 상위 15명과 영화를 많이 만든 상위 15명 배급사를 제외하고 나머지를 기타로 바꿔주는 함수
  df_distributor_filter = list(df.groupby(['배급사'])['전국매출액', '서울매출액'].mean().sort_values(by=['전국매출액'], ascending=[False])[:15].index) + list(df['배급사'].value_counts()[:15].index)
  for filter in df_distributor_filter:
    if (df_distributor_filter.count(x)>0):
      return x
    else:
      return '기타'

df['배급사'] = df['배급사'].apply(get_distributor)# 함수 적용

# 장르는 얼마 없기 때문에 매출액의 평균순으로 인코딩함
def get_country(x): #  매출액 상위 6개의 국가를 제외한 나머지를 기타로 바꿔주는 함수
  df_country_filter = list(df.groupby(['국적'])['전국매출액', '서울매출액'].mean().sort_values(by=['전국매출액'], ascending=[False])[:6].index)
  for filter in df_country_filter:
    if (df_country_filter.count(x)>0):
      return x
    else:
      return '기타'

df['국적'] = df['국적'].apply(get_country)# 함수 적용

 

 

나는 여기서 preprocessing을 모두 마친 dataframe의 heatmap을 출력해보았다.

의도했던 대로 전처리를 마친 column과 target값 사이에 음의 상관관계가 생겼다. 

하지만 나는 여기서 조금 아쉬움을 느꼈는데 바로 상관관계의 크기가 매우 작다는 것이었다. 

어떻게 하면 이 크기를 키울 수 있을까 고민을 하다. 

undersampling기법을 적용하기로 결정했다. (기타와 기타로 처리하지 않은 데이터간의 unbalance함이 있다고 판단)

 

나는 기타데이터를 3개이상 포함한 row를 모두 제거해주기로 결정했다.

 

<undersampling code>

def remove_row(x):
  preprocessing_col_list = [2, 3, 5, 10, 11]
  cnt_list = []
  for idx in range(x.shape[0]):
    cnt=0
    for col in preprocessing_col_list:
      if x.iat[idx, col] =='기타':
        cnt += 1
    if(cnt>2):  
      cnt_list.append(idx)
  return cnt_list

df_cnt = remove_row(df)

cnt값이 해당row의 기타데이터 개수가 되고 그 row의 index를 cnt_list에 append시켜 df.drop을 통해 제거시켜주었다.

 

그 후 각 column을 다시 rank encoding시켜주었다.

 

<전체 코드>

director_filter = list(df.groupby(['감독'])['전국매출액', '서울매출액'].mean().sort_values(by=['전국매출액'], ascending=[False])[:15].index) + list(df['감독'].value_counts().index[:15])
replace_dict = {}
for idx, director_name in enumerate(director_filter):
  replace_dict[director_name] = idx
df['감독'] = df['감독'].map(replace_dict)


distributor_filter = list(df.groupby(['배급사'])['전국매출액', '서울매출액'].mean().sort_values(by=['전국매출액'], ascending=[False])[:15].index) + list(df['배급사'].value_counts()[:15].index)
replace_dict = {}
for idx, distributor in enumerate(distributor_filter):
  replace_dict[distributor] = idx

df['배급사'] = df['배급사'].map(replace_dict)



film_rating_filter = ['12세이상관람가', '15세이상관람가', '전체관람가', '청소년관람불가']
replace_dict = {}
for idx, film_rating in enumerate(film_rating_filter):
  replace_dict[film_rating] = idx

df['등급'] = df['등급'].map(replace_dict)



genre_filter = list(df.groupby(['장르'])['전국매출액', '서울매출액'].mean().sort_values(by=['전국매출액'], ascending=[False]).index)
replace_dict = {}
for idx, genre in enumerate(genre_filter):
  replace_dict[genre] = idx

df['장르'] = df['장르'].map(replace_dict)



country_filter = list(df.groupby(['국적'])['전국매출액', '서울매출액'].mean().sort_values(by=['전국매출액'], ascending=[False])[:6].index)
replace_dict = {}
for idx, country in enumerate(country_filter):
  replace_dict[country] = idx
replace_dict['기타'] = 6
df['국적'] = df['국적'].map(replace_dict)

 

그 후 다시한번 heatmap을 출력해본 결과

상관관계의 크기가 이전보다 증가한 것을 확인할 수 있었다.

 

 

시행착오에 따른 예측 그래프 변화과정

 

그래프의 x축은 실제 target데이터, y축은 예측한 값이다. 따라서 y=x에 근접할 수록 model의 성능이 좋다고 판단가능

 

phase1은 label encoding만을 한 상태로 예측을 진행한 모습 굉장히 정확도가 낮은 것을 확인할 수 있다.

 

phase2는 rank encoding과 preprocessing을 적용시킨 후 예측을 진행한 그래프의 모습이다. 

이전에 비해 x축의 값이 작을 때 즉 전국매출액이 작은 data에 대해서 비교적 정확도가 높은 모습을 보이지만 x축의 값이 커질수록 정확도가 떨어지는 모습을 보이고 있다. 나는 이러한 결과가 나온 이유가 전국매출액이 높은 data에 대해서 

그 데이터의 개수가 적기 때문에 model이 편향된 값을 학습해 이러한 결과가 나왔다고 판단했고 따라서 target data의 편차를 줄이기 위해 log scaling을 사용하였다. 그리고 이전에 소개한 undersampling기법까지 적용한 결과 phase3의 그래프와 마찬가지로 결과가 매우 좋은 것을 확인할 수 있었다.

 

(참고로 예측에 사용한 모델은 xgb regressor이다.)

 

<예측 결과>

 

 

총 5개의 모델을 사용한 결과 상위 3가지 모델 모두 boosting모델이 차지한 것을 알 수 있었다.

 

나는 이러한 결과가 나온 이유가 XGBRegressor, LGBMRegressor, Random Forest는 앙상블 방식을 사용하여 예측을 

수행하기 때문에 다양한 결정 트리의 조합을 통해 예측을 개선하고, 편향과 분산을 줄여 데이터의 복잡한 패턴을 
더 잘 학습하고 예측할 수 있는 반면 Linear Regression, Ridge Regression, Lasso Regression은 변수의 중요도를 직접적으로 측정하지 않기 때문에, 모델이 데이터의 핵심 특성을 잘 파악하기 어려워 이러한 결과가 나왔다고 생각한다.

 

(score 측정방식은 mean absolute error이다.)

 

나는 subtask로 classification을 이용해서 성공한 영화 분류도 진행해보았다.

 

진행하기전 target data가 continuous variable이기 때문에 이를 ordinal한 데이터로 바꿔주는 과정이 필요했다.

나는 여기서 digitize함수를 통해 전체 target 데이터를 3등분한 후 순서에 따라 수익저조, 수익보통, 수익좋음 으로 

판별기준을 세웠다.

 

<분석결과>

분석결과는 다음과 같이 나왔다. 

 


느낀점 

 

예측을 수행해야 되는 데이터가 대부분 미래 데이터이기 때문에 실제 활용 가능한 데이터는 제한적이어서 정확도 향상에 어려움을 겪었다.
만약 영화데이터에 주연배우에 대한 정보가 포함되었다면 인지도가 있는 배우에 대해서 그 배우가 과거에 나왔던 영화의 총 매출액, 별점 등의 평균을 낼 수 있고 이렇게 얻은 정보로 정확도를 더 올릴 수 있지 않을까? 라는 생각을 했다.

데이터에 대한 아쉬움이 남았던 경험이다.

728x90
Comments