Python 3.10.2
conda 24.9.0
JupyterLab 4.2.5
지난 글에 이어 몇가지 주제들에 대해 더 분석을 진행한다.
이용한 데이터들에 정보는 [데이터 분석] DDA, EDA, CDA 실습 (1)글을 참고하자.
필요한 라이브러리를 import해주자.
import numpy as np
import pandas as pd
# 시각화
import seaborn as sns # 통계 전용 시각화
import matplotlib.pyplot as plt # 그래프 및 출력 옵션
import matplotlib as mpl # 환경설정(한글, -옵션 등)
mpl.rc("font", family="Malgun Gothic") # 한글 깨짐 방지
plt.rcParams["axes.unicode_minus"]=False # 마이너스 깨짐 방지
# 검증
import scipy.stats as stats # 통계 검증 lib
import statsmodels.api as sm
from statsmodels.stats.proportion import proportions_ztest # ztest
새로운 분석 주제에 대해 진행하자.
📌 분석 주제
- 지역별 평균 영화 관람객 수 차이 분석
- 영화 상영 횟수와 관람객 수의 관계 분석
- OTT 사용자 수와 영화 관람객 수 관계 분석
📌 각 주제별 진행 절차
- 1단계) DDA, 변수 검토 및 전처리
- 분석에 활용할 변수 전처리
- 변수의 특징 파악, 이상치와 결측치 정제
- 변수의 값을 다루기 편하게 바꾸기
- 2단계) EDA, 변수 간 관계 분석
- 변수 간 관계 분석
- 데이터 요약 표(groupby, pivot_table), 그래프 만들기
- 3단계) CDA, 통계적 가설 검정
- 가설 설정
- 가설 검증 절차
⭐ 지역별로 평균 영화 관람객 수의 차이가 있는지
- DDA: 분석에 활용할 변수(시도명, 영화관람객수) 전처리
- 이상치, 결측치 정제
- EDA: 변수 간 관게 분석
- 데이터 요약표: 지역별 영화관람객수 평균 요약표
- 그래프 만들기: x(지역(문자)), y(관람객수(숫자)) -> barplot
- CDA: 검증
- x(지역(문자)), y(관람객수(숫자)): 정규성 검증 -> 비정규 -> 3집단 이상 -> Kruskal Walis
지난번에 만들어 놓은 csv 데이터를 이용한다.
movie_data=pd.read_csv("../data/data_movie_viewing/movie_data.csv", encoding="cp949")
movie_data.head().T

이 데이터를 기준으로 분석을 진행한다.
이번 분석 주제는 지역별 평균 영화 관람객 수의 차이를 분석하기 때문에 순서는 다음과 같다.
1. "시도명" 이상치나 결측치 확인
2. "영화관람객수" 이상치나 결측치 확인 및 시각화
3. 지역별 영화관람객수 분석 -> "시도명"과 "영화관람객수" 그룹화
4. 통계적 가설 검정
시도명을 먼저 확인하자.
movie_data["시도명"].value_counts()
시도명
강원도 217
세종특별자치시 217
충청남도 217
제주특별자치도 217
전라북도 217
전라남도 217
인천광역시 217
울산광역시 217
서울특별시 217
경기도 217
부산광역시 217
대전광역시 217
대구광역시 217
광주광역시 217
경상북도 217
경상남도 217
충청북도 217
Name: count, dtype: int64
movie_data["시도명"].describe()
count 3689
unique 17
top 강원도
freq 217
Name: 시도명, dtype: object
movie_data["시도명"].isna().sum()
0
object형이며 결측치 없이 데이터 개수가 동일하다.
따라서 따로 시각화해줄 필요는 없다.
지난 주제에서는 서울특별시에 대한 데이터만 진행했기 때문에,
이번에는 전체를 다 보면 다음과 같다.
movie_data["영화관람객수"].info()
<class 'pandas.core.series.Series'>
RangeIndex: 3689 entries, 0 to 3688
Series name: 영화관람객수
Non-Null Count Dtype
-------------- -----
3689 non-null int64
dtypes: int64(1)
memory usage: 28.9 KB
통계량을 확인할때는 표준편차와 평균 등을 나타내기 때문에 float형으로 나타나니까 .astpye(int)를 붙여준다.
movie_data["영화관람객수"].describe().astype(int)
count 3689
mean 129285
std 225803
min 5
25% 22471
50% 55580
75% 131347
max 2194172
Name: 영화관람객수, dtype: int32
이를 보니 영화관람객수의 최소 min값이 5다.
이상치인거 같으니 우선 확인해보자.
min_value_data=movie_data[movie_data["영화관람객수"] == 5]
min_value_data

이렇게 나왔는데 혹시 다른 데이터들은 어떤지 확인하기 위해 앞 뒤로 5개씩 더 확인해보자.
surrounding_data=movie_data.iloc[1160:1170]
surrounding_data

이를 보니 이상치는 아니고, 2020년도 한참 코로나 시절이기 때문에 영화관람객수가 적은거 같다.
따라서 결측치가 있는지만 확인해보고 시각화해보자.
movie_data["영화관람객수"].isna().sum()
0
이제 연속형(숫자)인 영화관람객수를 시각화하려면 histplot을 사용하면 된다.
sns.histplot(data=movie_data, x="영화관람객수", kde=True)

이상치, 결측치 확인
데이터 요약표, 시각화
이제 변수들의 전처리는 다 끝났으니 지역별 영화관람객수를 분석해보자.
이번에는 각 지역들의 영화관람객수의 평균으로 비교 분석하는 것이기 때문에,
groupby나 pivot table을 이용한다.
movie_data_view_mean=movie_data.groupby("시도명")["영화관람객수"].mean().reset_index(name="평균영화관람객수")
movie_data_view_mean

이를 만약 pivot_table로 표현해보면 다음과 같다.
movie_data_view_mean_pivot=movie_data.pivot_table(values="영화관람객수", index="시도명", aggfunc="mean", fill_value=0)
movie_data_view_mean_pivot.rename(columns={"영화관람객수": "평균영화관람객수"}, inplace=True)
movie_data_view_mean_pivot

이렇게 요약표를 만들 수 있으며, 이들을 시각화하면 다음과 같다.
plt.figure(figsize=(12, 8))
sns.barplot(x="시도명", y="평균영화관람객수", data=movie_data_view_mean_pivot.sort_values("평균영화관람객수", ascending=False))
plt.title("지역별 영화관람객수 평균")
plt.xlabel("평균 영화관람객수")
plt.ylabel("지역")
plt.xticks(rotation=45)
plt.show()

이를 가로 막대 그래프를 그려보면 다음과 같다.
sorted_data=movie_data_view_mean_pivot.sort_values(by="평균영화관람객수", ascending=True)
plt.figure(figsize=(12, 8))
plt.barh(sorted_data.index, sorted_data["평균영화관람객수"])
plt.title("지역별 영화관람객수 평균")
plt.xlabel("평균 영화관람객수")
plt.ylabel("지역")
plt.show()

이렇게 차이를 볼 수 있다.
지역별 영화관람객수가 지역에 따라 다를지
다변수, x(지역(범주(문자))), y(영화관람객수(연속(숫자)))
가설:
- 귀무가설: 지역별로 영화관람객수의 평균이 다르지 않다.
- 대립가설: 지역별로 영화관람객수의 평균이 다르다.
이제 이렇게 분석을 완료했으니 이제 CDA, 통계적 가설 검정을 해야한다.
정규성 검정 먼저 진행해자.
stats.normaltest(movie_data["영화관람객수"])
NormaltestResult(statistic=3113.5300635798403, pvalue=0.0)
pvalue 0.0 < 0.05 유의수준으로 비정규분포다.
그리고 다변수이므로 kruskal() 함수를 사용한다.
앞서 사용한 pivot_table을 이용하면,
movie_data_view_mean_pivot

stats.kruskal(*movie_data_view_mean_pivot["평균영화관람객수"])
KruskalResult(statistic=16.0, pvalue=0.4529608094869946)
pvalue 0.45 > 0.05 유의수준으로 대립가설이 거짓이고, 귀무가설이 채택된다.
이렇게하여 분석결과는 Kruskal Walis 검정을 통해지역별로 영화관람객수의 평균의 차이가 없다.(통계량: 16.0, pvalue > 0.05)가 나온다.
분석결과를 한장으로 요약하려면, 시각화한 사진과 지역별 영화관람객수의 평균 비교표 사진으로 연도별로 차이가 있다는 점, Kruskal Walis 검정 결과를 통해 가설까지 검증하면 된다.
⭐ 영화 상영 횟수에 따른 관람객 수
- DDA: 분석에 활용할 변수(일평균영화상영수, 영화관람객수) 전처리
- 이상치, 결측치 정제
- EDA: 변수 간 관게 분석
- 데이터 요약표: 일 평균 영화상영수에 따른 영화관람객수 요약표
- 그래프 만들기: x(일평균영화상영수(연속(숫자)), y(영화관람객수(연속(숫자)) -> scatterplot, lmplot, heatmap
- CDA: 검증
- x(일평균영화상영수(연속(숫자)), y(영화관람객수(연속(숫자)): 상관분석 -> 정규성 검증 -> 비정규 -> Spearman
먼저 데이터를 다시 불러오자.
movie_data=pd.read_csv("../data/data_movie_viewing/movie_data.csv", encoding="cp949")
movie_data.head().T

이 데이터를 기준으로 분석을 진행한다.
이번 분석 주제는 일평균영화상영수에 따른 영화관람객수의 변화를 분석한다.
순서는 다음과 같다.
1. "일평균영화상영수" 변수 이상치나 결측치 확인 및 시각화
2. "영화관람객수" 변수 이상치나 결측치 확인 및 시각화
3. 영화상영수에 따른 관람객수 분석
4. 통계적 가설 검정
이상치, 결측치 체크
먼저 일평균영화상영수에 대한 이상치 및 결측치를 확인하고 시각화한다.
movie_data.head()

movie_data["일평균영화상영수"].info()
<class 'pandas.core.series.Series'>
RangeIndex: 3689 entries, 0 to 3688
Series name: 일평균영화상영수
Non-Null Count Dtype
-------------- -----
3689 non-null int64
dtypes: int64(1)
memory usage: 28.9 KB
결측치는 없다.
movie_data["일평균영화상영수"].isna().sum()
0
이번에는 통계량을 확인하자.
movie_data["일평균영화상영수"].describe()
count 3689.000000
mean 26.881811
std 15.209407
min 1.000000
25% 18.000000
50% 24.000000
75% 31.000000
max 110.000000
Name: 일평균영화상영수, dtype: float64
이를 시각화하면 연속형(숫자)이기 때문에, histplot을 사용한다.
sns.histplot(data=movie_data, x="일평균영화상영수", kde=True)

이번에는 영화관람객수이다.
movie_data["영화관람객수"].info()
<class 'pandas.core.series.Series'>
RangeIndex: 3689 entries, 0 to 3688
Series name: 영화관람객수
Non-Null Count Dtype
-------------- -----
3689 non-null int64
dtypes: int64(1)
memory usage: 28.9 KB
결측치는 없다.
movie_data["영화관람객수"].isna().sum()
0
통계량은 다음과 같다.
movie_data["영화관람객수"].describe()
count 3.689000e+03
mean 1.292851e+05
std 2.258033e+05
min 5.000000e+00
25% 2.247100e+04
50% 5.558000e+04
75% 1.313470e+05
max 2.194172e+06
Name: 영화관람객수, dtype: float64
시각화해보자.
sns.histplot(data=movie_data, x="영화관람객수", kde=True)

이상치, 결측치 확인
데이터 요약표
이제 국내외 영화의 매출 차이 합계로 비교 분석하자.
연속형 데이터이기 때문에 scatterplot, implot, heatmap으로 표현가능하다.
sns.scatterplot(data=movie_data, x="일평균영화상영수", y="영화관람객수")

sns.lmplot(data=movie_data, x="일평균영화상영수", y="영화관람객수")

이제 필요한 컬럼만 고르고 나머지는 drop한 후 상관분석을 진행하자.
movie_data_copy=movie_data.copy()
movie_data_copy=movie_data_copy.drop(["기준분기",
"월주차수",
"주차수",
"한국영화매출금액",
"해외영화매출금액",
"영화매출금액"],
axis=1)
movie_data_copy_num=movie_data_copy.select_dtypes(include="number")
round(movie_data_copy_num.corr(), 2)

이를 heatmap으로 표현하면,
plt.figure(figsize=(25,15))
sns.heatmap(movie_data_copy_num.corr(), annot=True)
plt.show()

지역별 영화관람객수가 지역에 따라 다를지
다변수, x(일평균영화상영수(연속(숫자))), y(영화관람객수(연속(숫자)))
가설:
- 귀무가설: 영화상영수에 따라 관람객수는 다르지 않다.
- 대립가설: 영화상영수에 따라 관람객수가 다르다.
이제 이렇게 분석을 완료했으니 이제 CDA, 통계적 가설 검정을 해야한다.
정규성 검정 먼저 진행해자.
stats.normaltest(movie_data_copy_num["일평균영화상영수"])
NormaltestResult(statistic=1597.9961985079449, pvalue=0.0)
pvalue 0.0 < 0.05 유의수준으로 비정규분포이며,
stats.normaltest(movie_data_copy_num["영화관람객수"])
NormaltestResult(statistic=3113.5300635798403, pvalue=0.0)
이 또한, pvalue 0.0 < 0.05 유의수준으로 비정규분포이다.
따라서 이제 spearman 검증을 한다.
stats.spearmanr(movie_data_copy_num["일평균영화상영수"], movie_data_copy_num["영화관람객수"])
SignificanceResult(statistic=0.3949244441336163, pvalue=5.84286384656807e-138)
pvalue 0.00...584 < 0.05 이므로 유의수준으로 대립가설이 참이고, 귀무가설이 기각된다.
이렇게하여 분석결과는 Spearman 검정을 통해 일평균영화상영수에 따라 영화관람객수의 차이가 있다(통계량: 0.394, pvalue < 0.05)가 나온다.
분석결과를 한장으로 요약하려면, 시각화한 사진과 지역별 영화관람객수의 평균 비교표 사진으로 영화상영수에 따라 차이가 있다는 점, Spearman 검정 결과를 통해 가설까지 검증하면 된다.
⭐ OTT 사용자 수 변화 따른 영화 관람객 수에 차이가 있는지
- DDA: 분석에 활용할 변수(OTT 사용자 수, 영화관람객수) 전처리
- 이상치, 결측치 정제
- EDA: 변수 간 관게 분석
- 데이터 병합
- 데이터 요약표: OTT 사용자 수 및 영화관람객수 합계 요약표
- 그래프 만들기: x(OTT 사용자 수(연속(숫자)), y(영화관람객수(연속(숫자)) -> heatmap
- CDA: 검증
- x(OTT 사용자 수(연속(숫자)), y(영화관람객수(연속(숫자)): 상관분석 -> 정규성 검증 -> 정규 -> pearson
먼저 데이터를 다시 불러오자.
movie_data=pd.read_csv("../data/data_movie_viewing/movie_data.csv", encoding="cp949")
movie_data.head().T

ott_data=pd.read_csv("../data/data_monthly_ott_users/ott_user_data.csv", encoding="cp949")
ott_data

이번에는 이 2개의 데이터를 기준으로 분석을 진행한다.
이번 분석 주제는 OTT 사용자수 변화에 따른 영화관람객수의 변화를 분석한다.
순서는 다음과 같다.
1. OTT "사용자수" 변수 이상치나 결측치 확인 및 시각화
2. 영화 "영화관람객수" 변수 이상치나 결측치 확인 및 시각화
3. OTT 사용자 수 변화에 따른 영화관람객수 분석
4. 통계적 가설 검정
이상치, 결측치 체크
시각화
먼저 OTT 데이터에서의 사용자수에 대한 이상치 및 결측치를 확인하고 시각화한다.
ott_data.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 345 entries, 0 to 344
Data columns (total 4 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 순위수 345 non-null int64
1 앱명 345 non-null object
2 사용자수 345 non-null int64
3 기준년월 345 non-null int64
dtypes: int64(3), object(1)
memory usage: 10.9+ KB
ott_data.isna().sum()
순위수 0
앱명 0
사용자수 0
기준년월 0
dtype: int64
이를 보니 결측치는 없다.
통계량을 확인해보자.
ott_data["사용자수"].describe().astype(int)
count 345
mean 790566
std 3931654
min 3568
25% 11808
50% 28933
75% 119877
max 34833534
Name: 사용자수, dtype: int32
이를 시각화하면 연속형(숫자)이기 때문에, histplot을 사용한다.
plt.figure(figsize=(10,6))
sns.histplot(data=ott_data, x="사용자수", bins=30, kde=True)
plt.title('OTT 사용자 수 분포')
plt.xlabel('OTT 사용자 수')
plt.ylabel('빈도수')
plt.show()

이를 기준년월을 기준으로 OTT사용자수를 groupby한다.
ott_data_group=ott_data.groupby("기준년월")["사용자수"].sum().reset_index(name="OTT사용자수")
ott_data_group

ott_data_group.info()
ott_data_group.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 2 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 기준년월 5 non-null int64
1 OTT사용자수 5 non-null int64
dtypes: int64(2)
memory usage: 208.0 bytes
이를 다시 시각화하면 이번에는 기준년월이 연속형임과 동시에 순서형이기때문에 lineplot을 사용한다.
sns.lineplot(x="기준년월", y="OTT사용자수", data=ott_data_group)

이상치, 결측치 체크
시각화
이번에는 영화관람객수이다.
movie_data["영화관람객수"].info()
<class 'pandas.core.series.Series'>
RangeIndex: 3689 entries, 0 to 3688
Series name: 영화관람객수
Non-Null Count Dtype
-------------- -----
3689 non-null int64
dtypes: int64(1)
memory usage: 28.9 KB
결측치는 없다.
통계량은 다음과 같다.
movie_data["영화관람객수"].describe()
count 3.689000e+03
mean 1.292851e+05
std 2.258033e+05
min 5.000000e+00
25% 2.247100e+04
50% 5.558000e+04
75% 1.313470e+05
max 2.194172e+06
Name: 영화관람객수, dtype: float64
이를 시각화하면 다음과 같다.
sns.histplot(data=movie_data, x="영화관람객수", kde=True)

이제 OTT데이터와의 병합을 위해 기준년도를 기준으로 기준년월이라는 파생변수를 생성한다.
movie_data_copy=movie_data.copy()
# 기준년도 + "01"로 기준년월을 만들고, 1년을 더해서 OTT 데이터와 일치하게 조정
movie_data_copy["기준년도"] = movie_data_copy["기준년도"].astype(str)
# 영화 데이터에서 기준년도별로 합산 후, 기준년월을 기준년도 + 01로 생성
movie_data_copy_sum_by_year=movie_data_copy.groupby("기준년도")["영화관람객수"].sum().reset_index()
# 영화 데이터를 기준년월로 변환 (기준년도 + 01, 예: 201901 -> 202001)
movie_data_copy_sum_by_year["기준년월"]=(movie_data_copy_sum_by_year["기준년도"].astype(int) + 1).astype(str) + "01"
movie_data_copy_sum_by_year.head()

movie_data_copy_sum_by_year.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 3 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 기준년도 5 non-null object
1 영화관람객수 5 non-null int64
2 기준년월 5 non-null object
dtypes: int64(1), object(2)
memory usage: 248.0+ bytes
이제 기준년월 컬럼을 int형으로 변환하고, 사용하지않을 기준년도 컬럼은 drop한다.
movie_data_copy_sum_by_year["기준년월"]=movie_data_copy_sum_by_year["기준년월"].astype("int64")
movie_data_copy_sum_by_year=movie_data_copy_sum_by_year.drop("기준년도", axis=1)
movie_data_copy_sum_by_year.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 5 entries, 0 to 4
Data columns (total 2 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 영화관람객수 5 non-null int64
1 기준년월 5 non-null int64
dtypes: int64(2)
memory usage: 208.0 bytes
이제 이를 시각화하면
sns.lineplot(x="기준년월", y="영화관람객수", data=movie_data_copy_sum_by_year)

데이터 요약표
이제 OTT 데이터와 영화데이터를 merge로 병합한다.
ott_data_group_copy=ott_data_group.copy()
merged_data=pd.merge(movie_data_copy_sum_by_year, ott_data_group_copy, on='기준년월', how='inner')
merged_data.head()

이제 이들의 상관분석을 진행한다.
merged_data.corr()

heatmap으로 표현하면,
sns.heatmap(merged_data.corr(), annot=True)

OTT사용자수에 따라 영화관람객수가 다를지
다변수, x(OTT사용자수(연속(숫자))), y(영화관람객수(연속(숫자)))
가설:
- 귀무가설: OTT사용자수에 따라 관람객수는 다르지 않다.
- 대립가설: OTT사용자수에 따라 관람객수가 다르다.
이제 이렇게 분석을 완료했으니 이제 CDA, 통계적 가설 검정을 해야한다.
정규성 검정 먼저 진행해자.
단, 데이터의 개수가 5개 미만일때는 shapiro를 사용한다.
stats.shapiro(merged_data["OTT사용자수"])
ShapiroResult(statistic=0.8415798544883728, pvalue=0.2000480443239212)
stats.shapiro(merged_data["영화관람객수"])
ShapiroResult(statistic=0.8220371007919312, pvalue=0.14797262847423553)
정규분포이기 때문에 pearson 검정을 진행한다.
stats.pearsonr(merged_data["OTT사용자수"], merged_data["영화관람객수"])
PearsonRResult(statistic=-0.54887968282556, pvalue=0.45112031717444)
pvalue 0.45 > 0.05 유의수준이므로 대립가설은 거짓이고, 귀무가설이 채택된다.
이렇게하여 분석결과는 pearson 검정을 통해 OTT사용자수에 따른 영화관람객수의 차이는 없다(통계량: -0.548, pvalue > 0.05)가 나온다.
분석결과를 한장으로 요약하려면, 시각화한 사진과 기준년월별 OTT사용자수와 영화관람객수의 합계 요약표 사진, pearson 검정 결과를 통해 가설까지 검증하면 된다.
몇가지의 주제로 분석을 진행해봤는데, OTT 데이터를 병합하면서 뭔가 깔끔하게 되지 않은 느낌이다. 그래도 몇가지 주제가 더 생각나면 추가로 정리하겠다.
끝!