# Step 1. Matplotlib 등 업그레이드
!pip install matplotlib -U
!pip install seaborn -U
!pip install pandas -U
!pip install openpyxl -U
# Step 2. 한글 설치 및 사용 설정
!sudo apt-get install -y fonts-nanum
!sudo fc-cache -fv
!rm ~/.cache/matplotlib -rf
# Step 3. 셀 실행 후 런타임 재시작
# Step 4. 한글 글꼴 설정
import matplotlib.pyplot as plt
plt.rcParams['font.family']=['NanumGothic', 'sans-serif']
plt.rcParams['axes.unicode_minus'] = False
# 한글 설정 확인
%matplotlib inline
fig, ax = plt.subplots(figsize=(10,3))
ax.text(0.5, 0.5, "한글 테스트")
plt.show()
import numpy as np
import pandas as pd
import seaborn as sns
import requests
from copy import deepcopy
sns.set_context("talk")
sns.set_style("whitegrid")
pd.options.display.max_columns=50
# seaborn 설정에 의해 파괴되는 한글 설정을 재설정
plt.rcParams['font.family']=['NanumGothic', 'sans-serif']
plt.rcParams['axes.unicode_minus'] = False
# 영화 목록
url_movielist = "https://jehyunlee.github.io/about/src/2021_datastory_movie/df_movielist.pkl"
# # 국가별 데이터
url_nations = "https://jehyunlee.github.io/about/src/2021_datastory_movie/df_nations.pkl"
url_nationsY = "https://jehyunlee.github.io/about/src/2021_datastory_movie/df_nationsY.pkl"
# # 장르별 데이터
url_genres = "https://jehyunlee.github.io/about/src/2021_datastory_movie/df_genres.pkl"
url_genresY = "https://jehyunlee.github.io/about/src/2021_datastory_movie/df_genresY.pkl"
# 데이터 불러오기
df_movielist = pd.read_pickle(url_movielist)
df_nations = pd.read_pickle(url_nations)
df_nationsY = pd.read_pickle(url_nationsY)
df_genres = pd.read_pickle(url_genres)
df_genresY = pd.read_pickle(url_genresY)
# 박스오피스 데이터: KOBIS 데이터를 받아 header 일부 수정
url_bo = "https://jehyunlee.github.io/about/src/2021_datastory_movie/KOBIS_boxoffice.xlsx"
# 데이터 불러오기
df_bo = pd.read_excel(url_bo, engine="openpyxl")
df_bo.head(3)
# 결측치 제거
df_bo = df_bo.loc[df_bo["개봉일"] != 'NaT']
df_bo["개봉일"] = df_bo["개봉일"].astype(str)
df_bo["openYear"] = df_bo["개봉일"].apply(lambda x: x.split("-")[0])
df_bo = df_bo.loc[df_bo["openYear"] != 'NaT']
df_bo["openYear"] = df_bo["openYear"].astype(int)
# 전국 관객수 = 0, 서울 관객수 = 0 영화들. 이상치로 간주하여 제거.
list_bo_zeros = df_bo.loc[df_bo["서울 관객수"] == 0].loc[df_bo["전국 관객수"] == 0].query("1971 <= openYear <=2020")[["영화명", "국적", "openYear"]].sort_values("openYear").apply(lambda x: f"{x[0]} ({x[1]}, {x[2]})", axis=1)
print(list_bo_zeros.tolist())
print(df_bo.shape)
df_bo = df_bo.loc[list(set(df_bo.index) - set(list_bo_zeros.index))]
print(df_bo.shape)
# 박스오피스 데이터 저장
!mkdir data
df_bo.to_pickle("./data/df_bo.pkl")
# 1971-2020년 데이터 선별
df_bo = df_bo.query("1971 <= openYear <= 2020")
print(df_bo.shape)
# 영화목록 데이터
df_movie_bo = df_movielist[["movieCd", "movieNm", "openDt", "repNationNm"]]
df_movie_bo["movieNm"] = df_movie_bo["movieNm"].apply(lambda x: x.replace(" ", ""))
df_movie_bo["openDt"] = df_movie_bo["openDt"].astype(str)
df_movie_bo["openDt"] = df_movie_bo["openDt"].apply(lambda x: f"{x[:4]}-{x[4:6]}-{x[6:]}")
df_movie_bo["영화명_개봉일_대표국적"] = df_movie_bo[["movieNm", "openDt", "repNationNm"]].apply(lambda x: f"{x[0]}_{x[1]}_{x[2]}", axis=1)
df_movie_bo.head(3)
# 박스오피스 데이터
df_bo_genres = deepcopy(df_bo)
# 영화명 띄어쓰기 제거
df_bo_genres["영화명"] = df_bo_genres["영화명"].str.replace(' ', '')
# 장르 결합 key
df_bo_genres["영화명_개봉일_대표국적"] = df_bo_genres[["영화명", "개봉일", "국적"]].apply(lambda x: f"{x[0]}_{x[1]}_{x[2]}", axis=1)
df_bo_genres.head(3)
# 데이터 병합 전 Key 확인
list(set(df_movie_bo["영화명_개봉일_대표국적"]) - set(df_bo_genres["영화명_개봉일_대표국적"]))
# 박스오피스 확인: 천녀유혼
df_bo.loc[df_bo["영화명"]=="천녀유혼"]
# 영화목록 확인: 천녀유혼
df_movielist.loc[df_movielist["movieNm"]=="천녀유혼"]
# 박스오피스 확인: 품행제로
df_bo.loc[df_bo["영화명"]=="품행제로"]
# 영화목록 확인: 품행제로
df_movielist.loc[df_movielist["movieNm"]=="품행제로"]
df_bo_genres = df_movie_bo.merge(df_bo_genres[["영화명_개봉일_대표국적", "영화명", "개봉일", "서울 관객수", "전국 관객수", "국적", "openYear"]])
df_bo_genres = df_bo_genres.merge(df_genres.drop(["movieNm", "openYear"], axis=1), on="movieCd")
df_bo_genres = df_bo_genres.drop(["movieNm", "openDt", "repNationNm", "영화명_개봉일_대표국적"], axis=1)
print(df_bo_genres.shape)
df_bo_genres.head(3)
df_bo.shape
df_bo_genres.shape
# 영화 장르, 관객 확인: 천녀유혼
df_bo_genres.loc[df_bo_genres["영화명"]=="천녀유혼"]
# 영화 장르, 관객 확인: 품행제로
df_bo_genres.loc[df_bo_genres["영화명"]=="품행제로"]
# 데이터 백업
df_bo_genres.to_pickle("./data/df_bo_genres.pkl")
# 년도별 데이터 정리
df_boY = df_bo[["openYear", "전국 관객수", "서울 관객수"]].groupby("openYear").sum().reset_index()
df_boY.head()
# 한국영화, 해외영화
df_bo_kr = df_bo.query("국적 == '한국'")
df_boY_kr = df_bo_kr[["openYear", "전국 관객수", "서울 관객수"]].groupby("openYear").sum().reset_index()
df_bo_nkr = df_bo.query("국적 != '한국'")
df_boY_nkr = df_bo_nkr[["openYear", "전국 관객수", "서울 관객수"]].groupby("openYear").sum().reset_index()
df_boY_kr.head()
df_boY_nkr.head()
# 데이터 백업
df_bo_kr.to_pickle("./data/df_bo_kr.pkl")
df_boY_kr.to_pickle("./data/df_boY_kr.pkl")
df_bo_nkr.to_pickle("./data/df_bo_nkr.pkl")
df_boY_nkr.to_pickle("./data/df_boY_nkr.pkl")
# 전국 관객 수 없는 영화 확인
sns.histplot(df_bo.loc[df_bo["전국 관객수"]==0, "openYear"])
# 전국 관객 수 있는 영화 확인
ax = sns.histplot(df_bo.loc[df_bo["전국 관객수"]!=0, "openYear"])
ax.set_ylim(0, 10)
# 서울 관객 수 있는 영화 확인
sns.histplot(df_bo.loc[df_bo["서울 관객수"]!=0, "openYear"])
# 서울 관객 수 없는 영화 확인
sns.histplot(df_bo.loc[df_bo["서울 관객수"]==0, "openYear"])
# 서울 관객 수 없는 영화의 전국 관객 수
print(df_bo.loc[df_bo["서울 관객수"]==0, "전국 관객수"].max())
sns.histplot(df_bo.loc[df_bo["서울 관객수"]==0, "전국 관객수"])
df_bo.loc[df_bo["서울 관객수"]==0].sort_values("전국 관객수", ascending=False).head(10)
# 서울 관객 수 없는 영화 목록
df_bo.loc[df_bo["서울 관객수"] == 0]
# 서울 관객 수가 0인 작품은 지방에서만 개봉한 작품으로 간주. 정상 데이터 처리.
# 박스오피스 데이터: KOBIS 데이터를 받아 header 일부 수정
url_boM_2018 = "https://jehyunlee.github.io/about/src/2021_datastory_movie/KOBIS_movie_2018.xlsx"
# 데이터 불러오기
df_boM_2018 = pd.read_excel(url_boM_2018, engine="openpyxl")
# 박스오피스 데이터: KOBIS 데이터를 받아 header 일부 수정
url_boM_2019 = "https://jehyunlee.github.io/about/src/2021_datastory_movie/KOBIS_movie_2019.xlsx"
# 데이터 불러오기
df_boM_2019 = pd.read_excel(url_boM_2019, engine="openpyxl")
# 박스오피스 데이터: KOBIS 데이터를 받아 header 일부 수정
url_boM_2020 = "https://jehyunlee.github.io/about/src/2021_datastory_movie/KOBIS_movie_2020.xlsx"
# 데이터 불러오기
df_boM_2020 = pd.read_excel(url_boM_2020, engine="openpyxl")
df_boM = pd.concat([df_boM_2018, df_boM_2019, df_boM_2020], axis=0)
# 년월 컬럼 생성
df_boM["YearMonth"] = df_boM["년월"].dt.strftime("%Y. %m.")
print(df_boM.shape)
df_boM.head()
# 한국 점유율, 외국 점유율 계산식? : 점유율/전체 점유율
(df_boM["한국 관객수"]/df_boM["전체 관객수"])[:5]
# 매출액 vs 관객수
plt.scatter(df_boM["한국 관객수"], df_boM["한국 매출액"])
plt.scatter(df_boM["외국 관객수"], df_boM["외국 매출액"])
# 한국영화 관객 1인당 매출: 8366원
(df_boM["한국 매출액"]/df_boM["한국 관객수"]).mean()
# 해외영화 관객 1인당 매출: 8429원
(df_boM["외국 매출액"]/df_boM["외국 관객수"]).mean()
# 이미지 저장 폴더
!mkdir images
# 국가별 색상코드
c_kr = "darkblue" # 한국
c_etc = "0.7" # 기타, 해외
c_it = "g" # 이탈리아
c_cn = "darkred" # 중국
c_fr = "gold" # 프랑스
c_hk = "orangered" # 홍콩
c_gb = "orchid" # 영국
c_jp = "thistle" # 일본
c_us = "mediumpurple" # 미국
# 국가별 색상
N_colors = dict(zip(["한국", "기타", "해외", "미국", "일본", "홍콩", "프랑스", "영국"],
[c_kr, c_etc, c_etc, c_us, c_jp, c_hk, c_fr, c_gb]))
# 장르별 색상코드
cmap = plt.get_cmap("tab20")
c_drama = cmap(0/20)
c_romance = cmap(1/20)
c_action = cmap(2/20)
c_comedy = cmap(3/20)
c_thriller = cmap(12/20)
c_ero = cmap(13/20)
c_horror = cmap(6/20)
c_crime = cmap(7/20)
c_ani = cmap(8/20)
c_adv = cmap(9/20)
c_sf = cmap(10/20)
c_fantasy = cmap(11/20)
c_mistery = cmap(4/20)
c_docu = cmap(5/20)
c_family = cmap(14/20)
c_history = cmap(15/20)
c_war = cmap(16/20)
c_play = cmap(17/20)
c_musical = cmap(18/20)
c_western = cmap(19/20)
c_genres = [c_drama, c_romance, c_action, c_comedy, c_thriller, c_ero, c_horror, c_crime, c_ani, c_adv, c_sf, c_fantasy, c_mistery, c_docu, c_family, c_history, c_war, c_play, c_musical, c_western, c_etc]
# 장르 목록
genres_order = ['드라마', '멜로/로맨스', '액션', '코미디', '스릴러', '성인물', '공포', '범죄', '애니메이션',
'어드벤처', 'SF', '판타지', '미스터리', '다큐멘터리', '가족', '사극', '전쟁', '공연', '뮤지컬', '서부극', '기타']
# 장르별 색상
G_colors = dict(zip([f"G_{g}" for g in genres_order], c_genres))
fig, axs = plt.subplots(ncols=2, figsize=(14, 20), gridspec_kw={"width_ratios": [5, 1]}, constrained_layout=True, sharey=True)
font_title = {"fontweight":"bold", "color":"0.4"}
axs[0].set_title("국내 관객 동원 (억명)", fontdict=font_title, pad=16)
axs[1].set_title("한국 영화 관객 점유 (%) ", fontdict=font_title, pad=16)
portion_aspect0 = axs[0].get_position().height/axs[0].get_position().width
portion_aspect1 = axs[1].get_position().height/axs[1].get_position().width
yticks = [1971] + list(range(1975, 2025, 5))
axs[0].set_ylim(2020, 1971)
axs[0].set_yticks(yticks)
axs[0].set_yticklabels(yticks)
for ax in axs[1:]:
ax.set_xticks([0, 0.5, 1])
ax.set_xticklabels([0, "50%", "100%"])
fig_p0, ax_p0 = plt.subplots(figsize=(axs[0].get_position().height * 20, axs[0].get_position().width * 14), constrained_layout=True)
ax_p0.fill_between(df_boY_kr["openYear"], -df_boY_kr["전국 관객수"]/1e8, 0, label="한국 영화 전국 관객수", fc=c_kr, lw=3)
ax_p0.fill_between(df_boY_kr["openYear"], -df_boY_kr["서울 관객수"]/1e8, 0, label="한국 영화 서울 관객수", fc=c_kr, lw=2, alpha=0.5)
ax_p0.fill_between(df_boY_nkr["openYear"], df_boY_nkr["전국 관객수"]/1e8, 0, label="해외 영화 전국 관객수", fc=c_etc, lw=3)
ax_p0.fill_between(df_boY_nkr["openYear"], df_boY_nkr["서울 관객수"]/1e8, 0, label="해외 영화 서울 관객수", fc=c_etc, lw=2, alpha=0.5)
ax_p0.grid(False)
ax_p0.set_xlim(1971, 2020)
ax_p0.set_ylim(-1.5, 1.5)
yticks = np.linspace(-1.5, 1.5, 7)
ax_p0.set_yticks(yticks)
ax_p0.set_yticklabels([abs(y) for y in yticks])
ax_p0.axis(False)
fig_p0.savefig("./images/bo_year0.png", dpi=200)
fig_p1, ax_p1 = plt.subplots(figsize=(axs[1].get_position().height * 20, axs[1].get_position().width * 14), constrained_layout=True)
ax_p1.fill_between(df_boY_kr["openYear"],
df_boY_kr["서울 관객수"]/df_boY["서울 관객수"] * 100, 0,
fc="lightsteelblue")
ax_p1.plot(df_boY_kr.query("openYear >= 2004")["openYear"],
df_boY_kr.query("openYear >= 2004")["전국 관객수"]/df_boY.query("openYear >= 2004")["전국 관객수"] * 100,
marker="o", ms=5, c="b", alpha=1, lw=2)
for y in [25, 50, 75, 100]:
ax_p1.axhline(y, c="lightgray", zorder=-1, ls=":")
p_min = (df_boY_kr["서울 관객수"]/df_boY["서울 관객수"]).min()*100
p_argmin = (df_boY_kr["서울 관객수"]/df_boY["서울 관객수"]).argmin()
ax_p1.annotate(f"{p_min:.1f} %\n({1971+p_argmin})",
xy=(1971 + p_argmin, p_min), xytext=(1971 + p_argmin, p_min+30), c="r",
arrowprops={"arrowstyle":'-|>', "ec":"r", "fc":"r"}, rotation=90, ha="center"
)
ax_p1.text(2004, 15, "서울", c="w", fontweight="bold", rotation=90, ha="left")
ax_p1.text(2004, 70, "전국", c="b", fontweight="bold", rotation=90, ha="left")
ax_p1.set_xlim(1971, 2020)
ax_p1.set_ylim(0, 100)
ax_p1.axis(False)
fig_p1.savefig("./images/bo_year1.png", dpi=200)
# 역사적 사건들
def plot_history(year, text, text_x_shift=50, text_y=None, text_c="green", text_size="medium", text_fc="w", text_align="center",
c_h0 = "limegreen", c_h1 = "palegreen", alpha_h1=0.7, ax=axs):
if not text_y:
text_y = year-0.5
y_line = [year] * 100
if np.array(axs == None).any():
ax = plt.gca()
x0_line = np.linspace(ax.get_xbound()[1], ax.get_xbound()[0], 100)
x1_line = 0
ax_0 = ax
elif isinstance(ax, np.ndarray):
x0_line = np.linspace(axs[0].get_xbound()[1], axs[0].get_xbound()[0], 100)
x1_line = np.linspace(axs[1].get_xbound()[1], axs[1].get_xbound()[0], 100)
ax_0 = ax[0]
else:
x0_line = np.linspace(axs[0].get_xbound()[1], axs[0].get_xbound()[0], 100)
x1_line = 0
ax_0 = ax
# axs[0]
for i in range(99):
ax_0.plot(x0_line[i:i+2], y_line[i:i+2], c=c_h0,
solid_capstyle='butt', alpha=np.power(np.sin(i/100),6)*2, zorder=15)
ax_0.text(ax_0.get_xbound()[0]+text_x_shift, text_y, text, c=text_c, fontsize=text_size,
multialignment=text_align, ha="left",
bbox={"boxstyle":"square", "pad":0.4, "facecolor":text_fc, "edgecolor":"none", "linewidth":1})
# axs[1:]
if isinstance(x1_line, np.ndarray):
for ax_ in ax[1:]:
for i in range(99):
ax_.plot(x1_line[i:i+2], y_line[i:i+2], c=c_h1,
solid_capstyle='butt', alpha=alpha_h1, zorder=15)
### 조립
# axs[0]
fig0_img = plt.imread("./images/bo_year0.png")
fig0_img = fig0_img.swapaxes(0, 1)[:,::-1, :][10:-10,10:-10,:]
x0, x1 = -1.5, 1.5
y1, y0 = 1971, 2020
axs[0].imshow(fig0_img, extent=[x0, x1, y0, y1])
axs[0].set_aspect(abs(x1-x0)/abs(y1-y0)*portion_aspect0 * 20/14)
axs[0].grid(False)
xticks = np.arange(-1.5, 2, 0.5)
axs[0].set_xticks(xticks)
axs[0].set_xticklabels(abs(xticks))
axs[0].text(-0.75, 1972.5, f" 한국 영화:\n서울 관객 총 {df_boY_kr['서울 관객수'].sum()/1e8:0.2f}억명", ha="center", va="center",
bbox={"boxstyle":"round", "pad":0.4,
"facecolor":"aliceblue", "edgecolor":"k", "linewidth":3},
zorder=20)
axs[0].text(0.75, 1972.5, f" 해외 영화:\n서울 관객 총 {df_boY_nkr['서울 관객수'].sum()/1e8:0.2f}억명", ha="center", va="center",
bbox={"boxstyle":"round", "pad":0.4,
"facecolor":"0.95", "edgecolor":"k", "linewidth":3},
zorder=20)
#legend
axs[0].bar(0, 1, fc=c_kr, lw=2, label="서울", alpha=0.5)
axs[0].bar(0, 1, fc=c_kr, lw=2, label="전국")
axs[0].bar(0, 1, fc=c_etc, lw=2, label="서울", alpha=0.5)
axs[0].bar(0, 1, fc=c_etc, lw=2, label="전국")
handles, labels = axs[0].get_legend_handles_labels()
legend_kr = axs[0].legend(handles=handles[:2], labels=["한국 영화 (서울)", "한국 영화 (전국)"],
loc="upper left", bbox_to_anchor=(0.1, 0.9))
legend_nkr = axs[0].legend(handles=handles[2:], labels=["해외 영화 (서울)", "해외 영화 (전국)"],
loc="upper right", bbox_to_anchor=(0.9, 0.9))
axs[0].add_artist(legend_kr)
# axs[1]
fig1_img = plt.imread("./images/bo_year1.png")
fig1_img = fig1_img.swapaxes(0, 1)[:,::-1, :][10:-10,10:-10,:]
x0, x1 = 0, 1
y1, y0 = 1971, 2020
axs[1].imshow(fig1_img, extent=[x0, x1, y0, y1])
axs[1].set_aspect(abs(x1-x0)/abs(y1-y0)*portion_aspect1 * 20/14)
axs[1].grid(False)
# 전국통계
plot_history(2004, "전국통계\n(배급사 협조 사항 집계)", text_x_shift=0.05, text_fc='none', alpha_h1=0.3, text_align="left",
ax=axs)
# 통합전산망
plot_history(2011, "통합전산망 ", text_x_shift=0.05, text_fc='none', alpha_h1=0.3,
ax=axs)
display(fig)
fig.savefig("./images/bo_year.png", dpi=200)
# 서울관객 5천만
seoul_50k_year = df_boY.loc[df_boY["서울 관객수"] > 5e7].iloc[0]["openYear"]
seoul_50k_num = df_boY.loc[df_boY["서울 관객수"] > 5e7].iloc[0]["서울 관객수"]
# 전국관객 1억
nation_100m_year = df_boY.loc[df_boY["전국 관객수"] > 1e8].iloc[0]["openYear"]
nation_100m_num = df_boY.loc[df_boY["전국 관객수"] > 1e8].iloc[0]["전국 관객수"]
# 전국관객 2억
nation_200m_year = df_boY.loc[df_boY["전국 관객수"] > 2e8].iloc[0]["openYear"]
nation_200m_num = df_boY.loc[df_boY["전국 관객수"] > 2e8].iloc[0]["전국 관객수"]
fig, ax = plt.subplots(figsize=(10, 3), constrained_layout=True)
ax.stackplot(df_boY["openYear"], df_boY["전국 관객수"], colors=["b"], ec="b", lw=2, alpha=0.6)
ax.stackplot(df_boY["openYear"], df_boY["서울 관객수"], colors=["cornflowerblue"], lw=2, alpha=0.5)
ax.set_xlim(1971, 2020)
ax.set_title("년간 국내 관객 동원 (명)", fontdict=font_title, pad=16)
xticks = [1971, 1980, 1990, 2000, 2010, 2020]
ax.set_xticks(xticks)
ax.set_xticklabels(xticks)
yticks = [0, 50e6, 100e6, 200e6]
ax.set_yticks(yticks)
ax.set_yticklabels([0, "5천만", "1억", "2억"])
# Legend
ax.text(1972, 1.8e8, f" 서울관객 5천만 돌파 ({seoul_50k_year}): {format(seoul_50k_num, ',')} \n\n\n",
fontsize="small",
bbox={"boxstyle":"round", "pad":0.4, "facecolor":"w", "edgecolor":"gray"}, ha="left", va="top")
ax.text(1972, 1.29e8, f" 전국관객 1억 돌파 ({nation_100m_year}): {format(nation_100m_num, ',')}",
fontsize="small", ha="left", va="center")
ax.text(1972, 0.88e8, f" 전국관객 2억 돌파 ({nation_200m_year}): {format(nation_200m_num, ',')}",
fontsize="small", ha="left", va="center")
# 서울관객 5천만
ax.scatter(seoul_50k_year, seoul_50k_num,
s=100, ec="cornflowerblue", lw=2, c="w", zorder=10)
ax.scatter(1972.5, 1.7e8,
s=100, ec="cornflowerblue", lw=2, c="w", zorder=10)
# 전국관객 1억
ax.scatter(nation_100m_year, nation_100m_num,
s=100, ec="blue", lw=2, c="#FFFF00", zorder=10)
ax.scatter(1972.5, 1.29e8,
s=100, ec="blue", lw=2, c="#FFFF00", zorder=10)
ax.scatter(nation_200m_year, nation_200m_num,
s=100, ec="blue", lw=2, c="#FFAA00", zorder=10)
ax.scatter(1972.5, 0.88e8,
s=100, ec="blue", lw=2, c="#FFAA00", zorder=10)
ax.spines[["left", "top", "right"]].set_visible(False)
ax.grid(axis="x")
fig.savefig("./images/bo_year_record.png", dpi=200)
df_boY_kr.query('1971 <= openYear <= 1987')['서울 관객수']
## 1971-1987
# 한국영화 1편당 관객 수
num_per_movie_kr = df_boY_kr.query("1971 <= openYear <= 1987")["서울 관객수"]/df_nationsY.query("1971 <= openYear <= 1987")["N_한국"]
mean_kr = format(int(df_boY_kr.query('1971 <= openYear <= 1987')['서울 관객수'].sum()/df_nationsY.query('1971 <= openYear <= 1987')['N_한국'].sum()), ',')
# 해외영화 1편당 관객 수
num_per_movie_nkr = df_boY_nkr.query("1971 <= openYear <= 1987")["서울 관객수"]/df_nationsY.query("1971 <= openYear <= 1987")["해외"]
mean_nkr = format(int(df_boY_nkr.query('1971 <= openYear <= 1987')['서울 관객수'].sum()/df_nationsY.query('1971 <= openYear <= 1987')['해외'].sum()), ',')
fig, ax = plt.subplots(figsize=(10, 3), constrained_layout=True)
ax.plot(range(1971, 1988), num_per_movie_nkr, c=c_etc, lw=3)
ax.plot(range(1971, 1988), num_per_movie_kr, c=c_kr, lw=3)
xticks = [1971, 1975, 1979, 1983, 1987]
ax.set_xticks(xticks)
ax.set_xlim(1971, 1987)
ax.set_ylim(0, 3.5e5)
yticks = [0, 1e5, 2e5, 3e5]
ax.set_yticks(yticks)
ax.set_yticklabels([f"{y/1e4:.0f}만명" if y > 0 else "0" for y in yticks])
for y in yticks:
ax.axhline(y, c="lightgray", zorder=-1, ls=":")
ax.grid(False)
ax.spines[["left", "top", "right"]].set_visible(False)
# ax.legend(ncol=2, loc="lower right", bbox_to_anchor=(1, 0.8))
ax.text(1971.5, 2e5, f"해외 영화 평균: {mean_nkr}", c="k",
bbox={"fc":"w"})
ax.text(1981, 8e4, f"한국 영화 평균:{mean_kr}", c=c_kr,
bbox={"fc":"w"})
ax.set_title("영화 편당 관객동원 (서울 기준, 1971-1987, 명/편)", fontdict=font_title)
fig.savefig("./images/num_movie_mean_1971.png", dpi=200)
168422/32182
## 1988-1998
num_kr_1971 = df_nationsY.query("1971 <= openYear <= 1987")["N_한국"]
num_nkr_1971 = df_nationsY.query("1971 <= openYear <= 1987")["해외"]
num_kr_1988 = df_nationsY.query("1988 <= openYear <= 1998")["N_한국"]
num_nkr_1988 = df_nationsY.query("1988 <= openYear <= 1998")["해외"]
fig, ax = plt.subplots(figsize=(7, 3), constrained_layout=True)
ax.plot([0, 1], [num_kr_1971.mean(), num_kr_1988.mean()], marker="o", color=c_kr)
ax.plot([0, 1], [num_nkr_1971.mean(), num_nkr_1988.mean()], marker="o", color=c_etc)
xticks = [0, 1]
ax.set_xticks(xticks)
ax.set_xticklabels([])
ax.set_title("연간 평균 개봉작 수 변화 (편)", fontdict=font_title, pad=16)
ax.spines[["left", "top", "right"]].set_visible(False)
ax.grid(False)
ax.set_yticks([])
ax.text(0,-20, "1971-1987", ha="center", va="top",
fontsize="small", c="gray", transform=ax.transData)
ax.text(1,-20, "1988-1998", ha="center", va="top",
fontsize="small", c="gray", transform=ax.transData)
ax.text(0, num_kr_1971.mean()+20, f"{num_kr_1971.mean():.1f}",
ha="center", va="bottom", c="k")
ax.text(1, num_kr_1988.mean()-20, f"{num_kr_1988.mean():.1f}",
ha="center", va="top", c="k")
ax.text(0, num_nkr_1971.mean()-5, f"{num_nkr_1971.mean():.1f}",
ha="center", va="top", c="k")
ax.text(1, num_nkr_1988.mean()+20, f"{num_nkr_1988.mean():.1f}",
ha="center", va="bottom", c="k")
ax.text(0, 180,
f"한국 영화: {num_kr_1988.mean()/num_kr_1971.mean()*100 - 100:.0f} %",
fontsize="large", fontweight="bold", ha="center", color=c_kr)
ax.text(1, 180,
f"해외 영화: +{num_nkr_1988.mean()/num_nkr_1971.mean()*100 - 100:.0f} %",
fontsize="large", fontweight="bold", ha="center", color="k")
ax.set_ylim(0, 300)
fig.savefig("./images/num_change_1988.png", dpi=200)
## 1988-1998
year0 = 1988
year1 = 1998
# 한국영화 1편당 관객 수
num_per_movie_kr = df_boY_kr.query(f"{year0} <= openYear <= {year1}")["서울 관객수"]/df_nationsY.query(f"{year0} <= openYear <= {year1}")["N_한국"]
mean_kr = format(int(df_boY_kr.query(f'{year0} <= openYear <= {year1}')['서울 관객수'].sum()/df_nationsY.query(f'{year0} <= openYear <= {year1}')['N_한국'].sum()), ',')
# 해외영화 1편당 관객 수
num_per_movie_nkr = df_boY_nkr.query(f"{year0} <= openYear <= {year1}")["서울 관객수"]/df_nationsY.query(f"{year0} <= openYear <= {year1}")["해외"]
mean_nkr = format(int(df_boY_nkr.query(f'{year0} <= openYear <= {year1}')['서울 관객수'].sum()/df_nationsY.query(f'{year0} <= openYear <= {year1}')['해외'].sum()), ',')
fig, ax = plt.subplots(figsize=(10, 3), constrained_layout=True)
ax.plot(range(year0, year1+1), num_per_movie_nkr, c=c_etc, lw=3)
ax.plot(range(year0, year1+1), num_per_movie_kr, c=c_kr, lw=3)
# xticks = [1971, 1975, 1980, 1985, 1987]
# ax.set_xticks(xticks)
ax.set_xlim(year0, year1)
ax.set_ylim(0, 3.5e5)
yticks = [0, 1e5, 2e5, 3e5]
ax.set_yticks(yticks)
ax.set_yticklabels([f"{y/1e4:.0f}만명" if y > 0 else "0" for y in yticks])
for y in yticks:
ax.axhline(y, c="lightgray", zorder=-1, ls=":")
ax.grid(False)
ax.spines[["left", "top", "right"]].set_visible(False)
# ax.legend(ncol=2, loc="lower right", bbox_to_anchor=(1, 0.8))
ax.text(year0 +0.5, 1e5, f"해외 영화 평균: {mean_nkr}", c="k",
bbox={"fc":"w"})
ax.text(year1-4, 1e5, f"한국 영화 평균:{mean_kr}", c=c_kr,
bbox={"fc":"w"})
ax.set_title(f"영화 편당 관객동원 (서울 기준, {year0}-{year1}, 명/편)", fontdict=font_title)
fig.savefig(f"./images/num_movie_mean_{year0}.png", dpi=200)
50166/39724
# 한국영화 1편당 관객 수 (1988-1992)
year0, year1 = 1988, 1992
num_per_movie_kr = df_boY_kr.query(f"{year0} <= openYear <= {year1}")["서울 관객수"]/df_nationsY.query(f"{year0} <= openYear <= {year1}")["N_한국"]
mean_kr = format(int(df_boY_kr.query(f'{year0} <= openYear <= {year1}')['서울 관객수'].sum()/df_nationsY.query(f'{year0} <= openYear <= {year1}')['N_한국'].sum()), ',')
print(mean_kr)
# 한국영화 1편당 관객 수 (1993-1995)
year0, year1 = 1993, 1995
num_per_movie_kr = df_boY_kr.query(f"{year0} <= openYear <= {year1}")["서울 관객수"]/df_nationsY.query(f"{year0} <= openYear <= {year1}")["N_한국"]
mean_kr = format(int(df_boY_kr.query(f'{year0} <= openYear <= {year1}')['서울 관객수'].sum()/df_nationsY.query(f'{year0} <= openYear <= {year1}')['N_한국'].sum()), ',')
print(mean_kr)
# 한국영화 1편당 관객 수 (1988-1995)
year0, year1 = 1988, 1995
num_per_movie_kr = df_boY_kr.query(f"{year0} <= openYear <= {year1}")["서울 관객수"]/df_nationsY.query(f"{year0} <= openYear <= {year1}")["N_한국"]
mean_kr = format(int(df_boY_kr.query(f'{year0} <= openYear <= {year1}')['서울 관객수'].sum()/df_nationsY.query(f'{year0} <= openYear <= {year1}')['N_한국'].sum()), ',')
print(mean_kr)
# 한국영화 1편당 관객 수 (1996-1998)
year0, year1 = 1996, 1998
num_per_movie_kr = df_boY_kr.query(f"{year0} <= openYear <= {year1}")["서울 관객수"]/df_nationsY.query(f"{year0} <= openYear <= {year1}")["N_한국"]
mean_kr = format(int(df_boY_kr.query(f'{year0} <= openYear <= {year1}')['서울 관객수'].sum()/df_nationsY.query(f'{year0} <= openYear <= {year1}')['N_한국'].sum()), ',')
print(mean_kr)
sj_x = ["2001.01.04\n명필름 (JSA 제작사)", "2001.04.23.\n한국영화제작가협회", "2021.09.\nKOBIS 최종"]
sj_s = [2.448399, 6.209893, 2.448899]
sj_j = [2.500000, 5.830228, 2.513540]
fig = plt.figure(figsize=(10, 4), constrained_layout=True)
axd = fig.subplot_mosaic(
"""
AC
BD
""",
gridspec_kw={"width_ratios":[2, 0.4], "wspace":0.01, "hspace":0.01})
#
c_s = "blue"
c_j = "sandybrown"
axd["A"].scatter(sj_x[:2], sj_s[:2], marker="o", s=200, c=c_s, ec="w", lw=1, label="쉬리")
axd["A"].scatter(sj_x[:2], sj_j[:2], marker="D", s=200, c=c_j, ec="w", lw=1, label="JSA")
axd["D"].scatter(sj_x[2], sj_s[2], marker="o", s=200, c=c_s, ec="w", lw=1)
axd["D"].scatter(sj_x[2], sj_j[2], marker="D", s=200, c=c_j, ec="w", lw=1)
axd["A"].set_xlim(-0.5, 1.5)
axd["A"].set_ylim(5.600000, 6.500000)
axd["C"].set_ylim(5.600000, 6.500000)
axd["A"].set_yticks([6])
axd["A"].set_yticklabels(["600만"])
axd["A"].set_xticks([])
axd["C"].set_yticks([])
axd["A"].grid(False)
axd["C"].grid(False)
axd["C"].set_xticks([])
axd["B"].scatter(sj_x[:2], sj_s[:2], marker="o", s=200, c=c_s, ec="w", lw=1)
axd["B"].scatter(sj_x[:2], sj_j[:2], marker="D", s=200, c=c_j, ec="w", lw=1)
axd["B"].set_yticks([2.5])
axd["B"].set_yticklabels(["250만"])
axd["B"].set_xlim(-0.5, 1.5)
axd["B"].set_ylim(2, 2.90000)
axd["D"].set_ylim(2, 2.90000)
axd["D"].set_yticks([])
axd["C"].set_xlim(-0.5, 0.5)
axd["D"].set_xlim(-0.5, 0.5)
axd["B"].grid(False)
axd["D"].grid(False)
axd["A"].spines[["bottom", "right"]].set_visible(False)
axd["B"].spines[["top", "right"]].set_visible(False)
axd["C"].spines[["bottom", "left"]].set_visible(False)
axd["D"].spines[["top", "left"]].set_visible(False)
axd["B"].set_facecolor("0.7")
axd["D"].set_facecolor("0.7")
fig.suptitle("쉬리 vs JSA 관객 수 (명)\n", fontweight="bold", color="gray")
axd["B"].text(sj_x[0], sj_s[0]-0.15, format(int(sj_s[0]*1e6), ","), va="top", ha="center", color=c_s)
axd["B"].text(sj_x[0], sj_s[0]+0.2, format(int(sj_j[0]*1e6), ","), va="bottom", ha="center", color="w")
axd["D"].text(sj_x[2], sj_s[2]-0.15, format(int(sj_s[2]*1e6), ","), va="top", ha="center", color=c_s)
axd["D"].text(sj_x[2], sj_s[2]+0.2, format(int(sj_j[2]*1e6), ","), va="bottom", ha="center", color="w")
axd["A"].text(sj_x[1], sj_s[1]+0.1, format(int(sj_s[1]*1e6), ","), va="bottom", ha="center", color=c_s)
axd["A"].text(sj_x[1], sj_j[1]-0.15, format(int(sj_j[1]*1e6), ","), va="top", ha="center", color=c_j)
def plot_slash(ax_key, x, y, dx=0.02, dy=0.04):
kwargs = dict(color='k', clip_on=False)
axd[ax_key].plot((x-dx, x+dx), (y-dy, y+dy), **kwargs, zorder=3)
return axd[ax_key]
plot_slash("A", axd["A"].get_xbound()[0], axd["A"].get_ybound()[0])
plot_slash("A", axd["A"].get_xbound()[1], axd["A"].get_ybound()[1])
plot_slash("B", axd["B"].get_xbound()[0], axd["B"].get_ybound()[1])
plot_slash("B", axd["B"].get_xbound()[1], axd["B"].get_ybound()[0])
plot_slash("C", axd["C"].get_xbound()[0], axd["C"].get_ybound()[1], dx=0.05)
plot_slash("C", axd["C"].get_xbound()[1], axd["C"].get_ybound()[0], dx=0.05)
plot_slash("D", axd["D"].get_xbound()[0], axd["D"].get_ybound()[0], dx=0.05)
plot_slash("D", axd["D"].get_xbound()[1], axd["D"].get_ybound()[1], dx=0.05)
axd["A"].text(0.5, 6, "전국 관객", fontweight="bold", color="gray", ha="center")
axd["B"].text(0.5, 2.4, "서울 관객", fontweight="bold", color="w", ha="center")
axd["A"].legend(loc="upper left")
fig.savefig("./images/swiri_jsa.png", dpi=200)
fig, ax = plt.subplots(figsize=(10, 4), constrained_layout=True)
ax.bar(df_boM["YearMonth"], df_boM["전체 관객수"], width=1, ec='none',
color=[c_etc]*36, label="해외 영화")
ax.bar(df_boM["YearMonth"], df_boM["한국 관객수"], width=1, ec='none',
color=[c_kr]*36, label="한국 영화")
for y in range(3):
for m in range(12):
ax.text(12*y + m, -1e6, f"{m+1}", fontsize="x-small", c="gray", ha="center", va="top")
ax.set_xlim(-0.5, 35.5)
ax.set_xticks([5, 17, 29])
ax.set_xticklabels([2018, 2019, 2020], va="top")
ax.tick_params(axis="x", pad=12)
ax.set_ylim(0, 4.5e7)
yticks=np.arange(0, 4e7, 1e7)
ax.set_yticks(yticks)
ax.set_yticklabels([f"{y/1e7:.0f}천만" if y > 0 else "0" for y in yticks])
ax.grid(False)
ax.set_title("월간 국내 관객 동원 (명)", fontdict=font_title, pad=16)
ax.spines[["left", "top", "right"]].set_visible(False)
for y in yticks:
ax.axhline(y, c="gray", zorder=-1, alpha=0.2)
ax.axvline(11.5, c="w")
ax.axvline(23.5, c="w")
# 발병
ax.axvline(22, c="r", alpha=0.5)
ax.axvline(24, c="r", alpha=0.5)
ax.plot([25, 25], [0, 2.2e7], c="r", alpha=0.5)
ax.text(22, 3.8e7, "COVID19 최초 감염 보고 \n(2019.11.17.) ", ha="right", fontsize="small", c="r",
bbox={"pad":0, "fc":"w"}, zorder=-0.5)
ax.text(24, 3.8e7, " 국내 최초 감염 보고\n (2020.01.20.)", ha="left", fontsize="small", c="r",
bbox={"pad":0, "fc":"w"}, zorder=-0.5)
ax.text(25, 2.8e7, " 신천지 집단 감염 보고\n (2020.02.18.)", ha="left", fontsize="small", c="r",
bbox={"pad":0, "fc":"w"}, zorder=-0.5)
fig.legend(loc="lower left", bbox_to_anchor=(0.07, 0.7), fontsize="small")
fig.savefig("./images/bo_covid19.png", dpi=200)
def plot_boS_year(year0, year1, nation, xmax=None, annot_pos=0.8, legend=False):
# 영화 편당 서울 관객 수 분포
if nation == '해외':
df_bo_distS = df_bo_genres.query("국적 != '한국'").groupby("openYear").agg(list)["서울 관객수"].reset_index()
if not xmax:
xmax = df_bo.query(f"{year0} <= openYear <= {year1}").query(f"국적 != '한국'")["서울 관객수"].max()
else:
df_bo_distS = df_bo_genres.query(f"국적 == '{nation}'").groupby("openYear").agg(list)["서울 관객수"].reset_index()
if not xmax:
xmax = df_bo.query(f"{year0} <= openYear <= {year1}").query(f"국적 == '{nation}'")["서울 관객수"].max()
# 국가별 색상
c_nation = N_colors[nation]
nrows = year1-year0+1
fig, axes = plt.subplots(nrows=nrows, ncols=2, figsize=(12, 0.7*nrows),
# sharex=True,
# sharey=True,
# gridspec_kw={"hspace":-0.5},
# gridspec_kw={"width_ratios":[3,1]},
# constrained_layout=True
)
# 왼쪽. KDE plot
z=0
for ax, year in zip(axes[:,0], range(year0, year1+1)):
ax.grid(False)
ax.set_yticks([0])
ax.set_yticklabels([""])
ax.text(0, 0, f"{year} ", transform=ax.transAxes, ha="right", va="bottom")
ax.set_ylabel("")
ax.set_zorder(z+10)
ax.set_facecolor("none")
ax.spines[["left", "top", "right"]].set_visible(False)
list_bo_year_ = df_bo_distS.query(f"openYear == {year}")["서울 관객수"].values
if len(list_bo_year_) == 0:
ax.set_ylim(0, np.finfo(float).eps)
continue
list_bo_year = list_bo_year_[0]
if len(list_bo_year) > 1:
sns.kdeplot(list_bo_year,
color=c_nation, ec="w", lw=2, cut=0, fill=True, ax=ax, alpha=1)
ax.set_ylabel("")
else:
ax.plot(list_bo_year * 2, [ax.get_ybound()[1]/2] * 2,
c=c_nation, lw=2, alpha=1)
ax.set_ylim(0, np.finfo(float).eps)
# 왼쪽 y축범위
ymax_all = max([ax.get_ybound()[1] for ax in axes[:,0]])
for ax, year in zip(axes[:,0], range(year0, year1+1)):
# x, y축 범위 설정
ax.set_xlim(0, xmax*1.05)
ax.set_ylim(0, ymax_all)
# xticklabels
xticks = [x for x in ax.get_xticks() if (ax.get_xbound()[0] <= x <= ax.get_xbound()[1])]
if year == year1:
ax.set_xticks(xticks)
ax.set_xticklabels([f"{x/10000:.0f}만" if x>0 else "0" for x in xticks])
else:
ax.set_xticks(xticks)
ax.set_xticklabels([])
list_bo_year_ = df_bo_distS.query(f"openYear == {year}")["서울 관객수"].values
if len(list_bo_year_) == 0:
continue
list_bo_year = list_bo_year_[0]
# 최다 관객
max_num = np.max(list_bo_year)
ax.plot([max_num]*2, [-0.3*ymax_all, 0.3*ymax_all], "ko-", lw=2, mfc="#FFFF00", mew=2)
# 해외
if nation == '해외':
max_movieNm = df_bo.query(f"openYear == {year}").loc[df_bo["서울 관객수"]==max_num].loc[df_bo["국적"]!='한국']["영화명"].values[0]
else:
max_movieNm = df_bo.query(f"openYear == {year}").loc[df_bo["서울 관객수"]==max_num].loc[df_bo["국적"]==nation]["영화명"].values[0]
if max_num > annot_pos*xmax:
ax.text(max_num, 0.1*ymax_all, f"{max_movieNm} ({format(max_num, ',')}) ",
ha="right", c="k", fontsize="small")
else:
ax.text(max_num, 0.1*ymax_all, f" {max_movieNm} ({format(max_num, ',')})",
ha="left", c="k", fontsize="small")
ax.set_ylim(0, )
### 관객 수 분포
for ax, year in zip(axes[:,1], range(year0, year1+1)):
ax.set_xlim(100, xmax*1.1)
ax.set_ylim(0, ymax_all)
ax.set_facecolor("none")
ax.spines[["left", "top", "right"]].set_visible(False)
ax.grid(False)
ax.set_yticks([])
ax.set_xscale("log")
# xticklabels
xticks = [x for x in ax.get_xticks() if (ax.get_xbound()[0] <= x <= ax.get_xbound()[1])]
xticks_minor = [x for x in ax.get_xticks(minor=True) if (ax.get_xbound()[0] < x < ax.get_xbound()[1])]
if year == year1:
ax.set_xticks(xticks)
ax.set_xticks(xticks_minor, minor=True)
ax.set_xticklabels([f"{x/10000:.0f}만" if x>1000 else f"{x/1000:.0f}천" if x>100 else f"{x/100:.0f}백" for x in xticks])
else:
ax.set_xticks(xticks)
ax.set_xticks(xticks_minor, minor=True)
ax.set_xticklabels([])
# xgrid
for x in xticks:
ax.plot([x, x], [0, ymax_all*0.4], c="lightgray", zorder=-2)
for x in xticks_minor:
ax.plot([x, x], [0, ymax_all*0.4], c="lightgray", lw=1, zorder=-2)
list_bo_year_ = df_bo_distS.query(f"openYear == {year}")["서울 관객수"].values
if len(list_bo_year_) == 0:
continue
list_bo_year = list_bo_year_[0]
if len(list_bo_year) > 1:
ymax = ax.get_ybound()[1]
list_bo_year = np.array(list_bo_year)
ax.plot([list_bo_year.min(), np.quantile(list_bo_year, 0.1)], [0.25*ymax, 0.25*ymax],
c="darkred", lw=3, solid_capstyle='butt', label="하위10% 이하", zorder=-1)
ax.plot([np.quantile(list_bo_year, 0.1), np.median(list_bo_year)], [0.25*ymax, 0.25*ymax],
c="r", lw=13, solid_capstyle='butt', label="하위10%-중간값")
ax.plot([np.median(list_bo_year), np.quantile(list_bo_year, 0.9)], [0.25*ymax, 0.25*ymax],
c="b", lw=13, solid_capstyle='butt', label="중간값-상위10%")
ax.plot([np.quantile(list_bo_year, 0.9), list_bo_year.max()], [0.25*ymax, 0.25*ymax],
c="darkblue", lw=3, solid_capstyle='butt', label="상위10% 이상", zorder=-1)
ax.plot([np.median(list_bo_year), np.median(list_bo_year)], [0, 0.4*ymax], c="w", lw=1)
ax.scatter([list_bo_year.max()], [0.25*ymax], c="k", lw=2, fc="#FFFF00", ec="k", s=100, label="최대관객")
ax.set_ylabel("")
elif len(list_bo_year) == 1:
ax.scatter([list_bo_year[0]], [0.25*ymax], c="k", lw=2, fc="#FFFF00", ec="k", s=100, label="최대관객")
else:
ax.plot(list_bo_year * 2, [ax.get_ybound()[1]/2] * 2,
c=c_nation, lw=2, alpha=1)
handles, labels = axes[0, 1].get_legend_handles_labels()
if legend:
fig.legend(handles=handles[:5], labels=labels[:5],
loc="upper center", bbox_to_anchor=(0.5,1), frameon=True,
fontsize="small", ncol=5)
suptitle = " "
fig.suptitle(suptitle,
fontweight="bold", color="0.4")
fig.subplots_adjust(hspace=-0.5, wspace=0.1, left=0.06, bottom=0.15, top=0.95, right=0.95)
else:
suptitle = f"{nation} 영화 (서울 관객 기준, 명)"
fig.suptitle(suptitle,
fontweight="bold", color="0.4")
fig.subplots_adjust(hspace=-0.5, wspace=0.1, left=0.06, bottom=0.05, top=0.95, right=0.95)
fig.savefig(f"./images/bo_year_{year0}-{year1}_{nation}.png", dpi=200)
nation = '홍콩'
df_bo_distS = df_bo_genres.query(f"국적 == '{nation}'").groupby("openYear").agg(list)["서울 관객수"].reset_index()
year = 1979
list_bo_year_ = df_bo_distS.query(f"openYear == {year}")["서울 관객수"].values
list_bo_year = list_bo_year_[0]
c_nation = c_hk
sns.kdeplot(list_bo_year,
color=c_nation, ec="w", lw=2, cut=0, fill=True, alpha=1)
plot_boS_year(1999, 2003, '한국', annot_pos=0.63, legend=True, xmax=4e6)
def get_median(year, nation):
df_median = df_bo_genres.query(f"국적 == '{nation}'").query(f"openYear == {year}")
m = df_median["서울 관객수"].median()
if df_median.loc[df_median["서울 관객수"] > m, "서울 관객수"].shape[0] > 0:
m_idx = np.argmin(df_median.loc[df_median["서울 관객수"] > m, "서울 관객수"])
results = df_median.loc[df_median["서울 관객수"] > m].iloc[m_idx].values
return df_median.shape[0], results[1], results[3]
for y in range(1999, 2004):
print(get_median(y, "한국"))
for y in range(1971, 1988):
print(get_median(y, "영국"))
plot_boS_year(1971, 1987, '한국', annot_pos=0.6, xmax=1e6)
for y in range(1971, 1988):
print(get_median(y, "한국"))
df_bo_genres.query("국적 == '한국'").query("openYear <= 1987").loc[df_bo_genres["서울 관객수"] < 100]
plot_boS_year(1971, 1987, '해외', annot_pos=0.7, xmax=1e6)
plot_boS_year(1971, 1987, '미국', annot_pos=0.9, xmax=1e6)
for y in range(1971, 1988):
print(get_median(y, "미국"))
plot_boS_year(1971, 1987, '홍콩', annot_pos=0.7, xmax=1e6)
for y in range(1971, 1988):
print(get_median(y, "홍콩"))
plot_boS_year(1971, 1987, '프랑스', annot_pos=0.7, xmax=1e6)
for y in range(1971, 1988):
print(get_median(y, "프랑스"))
# 킬링필드 국적 확인: 영국
df_bo.query("영화명 == '킬링필드'")
# 킬링필드 국적 확인: 영국
df_bo.query("영화명 == '미션'")
plot_boS_year(1971, 1987, '영국', annot_pos=0.7, xmax=1e6)
plot_boS_year(1988, 1998, '한국', annot_pos=0.6, xmax=2.2e6)
for y in range(1988, 1998):
print(get_median(y, "한국"))
plot_boS_year(1988, 1998, '해외', annot_pos=0.6, xmax=2.2e6)
nation = "한국"
median_kr = df_bo_genres.query(f"국적 == '한국'").groupby("openYear").agg(np.median)["서울 관객수"].reset_index()
median_us = df_bo_genres.query(f"국적 == '미국'").groupby("openYear").agg(np.median)["서울 관객수"].reset_index()
median_nkr = df_bo_genres.query(f"국적 != '한국'").groupby("openYear").agg(np.median)["서울 관객수"].reset_index()
from matplotlib.patches import Rectangle
fig, ax = plt.subplots(figsize=(12, 5), constrained_layout=True)
ax.plot(median_kr["openYear"], median_kr["서울 관객수"], c=c_kr)
ax.plot(median_us["openYear"], median_us["서울 관객수"], c=c_us)
ax.plot(median_nkr["openYear"], median_nkr["서울 관객수"], c=c_etc)
ax.set_xlim(1971, 2000)
ax.set_title("연간 관객 중간값 (명)", fontdict=font_title, pad=16)
ax.spines[["top", "left", "right"]].set_visible(False)
ax.grid(axis="x")
xticks = [1971, 1980, 1990, 2000]
ax.set_xticks(xticks)
ax.set_xticklabels(xticks)
ax.set_ylim(0, 4e5)
ax.set_yticks([0, 1e5, 2e5])
ax.set_yticklabels([0, "십만", "2십만"])
rec = Rectangle([1988, 5e3], 10, 4e4, fc="none", ec="brown", zorder=10)
ax.add_artist(rec)
ax_big = fig.add_axes([0.58, 0.45, 0.35, 0.35])
ax_big.plot(median_kr["openYear"], median_kr["서울 관객수"], lw=4, c=c_kr)
ax_big.plot(median_us["openYear"], median_us["서울 관객수"], lw=4, c=c_us)
ax_big.plot(median_nkr["openYear"], median_nkr["서울 관객수"], lw=4, c=c_etc)
ax_big.set_xlim(1988, 1998)
ax_big.set_ylim(0,40000)
ax_big.grid(False)
ax_big.spines[["top", "left", "right", "bottom"]].set_edgecolor("k")
ax_big.spines[["top", "left", "right", "bottom"]].set_linewidth(3)
ax_big.set_xticks([1988, 1992, 1996, 1998])
ax_big.set_xticklabels([1988, 1992, 1996, 1998], color="k", fontweight="bold")
ax_big.set_yticks([0, 2e4, 4e4])
ax_big.set_yticklabels([0, "2만", "4만"], color="k", fontweight="bold")
ax.axvspan(1988, 1998, fc="#FFFF00", alpha=0.3)
ax.text(1978, 8e4, "한국 영화", c=c_kr)
ax.text(1982, 2.8e5, "미국 영화", c=c_us)
ax.text(1978, 3e5, "해외 영화", c="0.4")
fig.savefig("./images/bo_median_1971_2000.png", dpi=200)
plot_boS_year(1988, 1998, '미국', annot_pos=0.6, xmax=2.2e6)
plot_boS_year(1988, 1998, '일본', annot_pos=0.6, xmax=2.2e6)
plot_boS_year(1971, 1998, '홍콩', annot_pos=0.6, xmax=2.2e6)
plot_boS_year(1988, 1998, '프랑스', annot_pos=0.6, xmax=2.2e6)
plot_boS_year(1988, 1998, '영국', annot_pos=0.6, xmax=2.2e6)
plot_boS_year(1999, 2010, '한국', annot_pos=0.6, xmax=4e6)
plot_boS_year(1999, 2010, '해외', annot_pos=0.6, xmax=4e6)
fig, ax = plt.subplots(figsize=(12, 5), constrained_layout=True)
ax.plot(median_kr["openYear"], median_kr["서울 관객수"], c=c_kr)
ax.plot(median_us["openYear"], median_us["서울 관객수"], c=c_us)
ax.plot(median_nkr["openYear"], median_nkr["서울 관객수"], c=c_etc)
ax.set_xlim(1996, 2012)
ax.set_title("연간 관객 중간값 (명)", fontdict=font_title, pad=16)
ax.spines[["top", "left", "right"]].set_visible(False)
ax.grid(axis="x")
xticks = [1996, 2000, 2004, 2008, 2012]
ax.set_xticks(xticks)
ax.set_xticklabels(xticks)
ax.set_ylim(0, 2e5)
ax.set_yticks([0, 1e5, 2e5])
ax.set_yticklabels([0, "10만", "20만"])
rec = Rectangle([2005, 5e3], 4, 9e4, fc="none", ec="brown", zorder=10)
ax.add_artist(rec)
ax_big = fig.add_axes([0.1, 0.42, 0.25, 0.45])
ax_big.plot(median_kr["openYear"], median_kr["서울 관객수"], lw=4, c=c_kr)
ax_big.plot(median_us["openYear"], median_us["서울 관객수"], lw=4, c=c_us)
ax_big.plot(median_nkr["openYear"], median_nkr["서울 관객수"], lw=4, c=c_etc)
ax_big.set_xlim(2005, 2009)
ax_big.set_ylim(0,100000)
ax_big.grid(False)
ax_big.spines[["top", "left", "right", "bottom"]].set_edgecolor("k")
ax_big.spines[["top", "left", "right", "bottom"]].set_linewidth(3)
ax_big.set_xticks([2005, 2007, 2009])
ax_big.set_xticklabels([2005, 2007, 2009], color="k", fontweight="bold")
ax_big.set_yticks([0, 5e4, 1e5])
ax_big.set_yticklabels([0, "5만", "10만"], color="k", fontweight="bold")
ax.axvspan(1999, 2010, fc="#FFFF00", alpha=0.3)
ax.text(2003, 1.8e5, "한국 영화", c=c_kr)
ax.text(2003, 0.7e5, "미국 영화", c=c_us)
ax.text(2006, .2e5, "해외 영화", c="0.4")
fig.savefig("./images/bo_median_1996_2012.png", dpi=200)
plot_boS_year(1999, 2010, '미국', annot_pos=0.6, xmax=4e6)
plot_boS_year(1971, 2010, '홍콩', annot_pos=0.6, xmax=2.2e6)
df_bo.query("영화명 == '아저씨'")
df_bo.query("영화명 == '인셉션'")
plot_boS_year(1998, 2020, '일본', annot_pos=0.6, xmax=1.3e6)
# 일본영화 장르 변화 확인 : 총 4279편
df_bo_genres_jp = df_bo_genres.query("국적 == '일본'")
print(df_bo_genres_jp.shape)
df_bo_genres_jp.head()
# 서울 관객 100명 이하 영화: 총 3319편
df_bo_genres_jp.loc[df_bo_genres_jp["서울 관객수"] < 100]
df_bo_genresY_jp100 = df_bo_genres_jp.loc[df_bo_genres_jp["서울 관객수"] < 100].groupby("openYear").sum().reset_index()
display(df_bo_genresY_jp100.head(3))
print(df_bo_genresY_jp100.filter(like="G_").sum().sort_values(ascending=False))
fig, axs = plt.subplots(figsize=(12, 5), nrows=2, sharex=True, constrained_layout=True)
# 장르별 영화 개봉 편수
axs[0].plot(df_bo_genres_jp.loc[df_bo_genres_jp["서울 관객수"] < 100].groupby("openYear").count().reset_index()["openYear"],
df_bo_genres_jp.loc[df_bo_genres_jp["서울 관객수"] < 100].groupby("openYear").count().reset_index()["서울 관객수"],
c="k", lw=3, label="개봉 영화 편수", zorder=2)
axs[0].stackplot(df_bo_genresY_jp100["openYear"],
[df_bo_genresY_jp100["G_성인물"], df_bo_genresY_jp100["G_드라마"], df_bo_genresY_jp100["G_멜로/로맨스"], df_bo_genresY_jp100["G_액션"], df_bo_genresY_jp100["G_스릴러"], df_bo_genresY_jp100["G_공포"], df_bo_genresY_jp100["G_애니메이션"]],
colors=[c_ero, c_drama, c_romance, c_action, c_thriller, c_horror, c_ani], lw=1,
labels=["성인물", "드라마", "멜로/로맨스", "액션", "스릴러", "공포", "애니메이션"])
axs[0].set_xlim(2000, 2020)
xticks = [2000, 2005, 2010, 2015, 2020]
axs[0].set_xticks(xticks)
axs[0].set_xticklabels(xticks)
axs[0].grid(False)
yticks = [0, 500]
axs[0].set_yticks(yticks)
axs[0].set_yticklabels(yticks)
for y in yticks:
axs[0].axhline(y, c="lightgray", zorder=-1)
axs[0].spines[["left", "top", "right"]].set_visible(False)
axs[0].set_title("년도별 개봉 일본 영화 수 (관객 수 100명 미만, 편)", pad=16, fontdict=font_title)
axs[0].legend(loc="upper left", fontsize="small", ncol=2)
# 관객 수
axs[1].set_title("년도별 일본 영화 관람객 수 (관객 수 100명 미만, 장르 누적, 명)", pad=16, fontdict=font_title)
df_bo_genres_jp100P = deepcopy(df_bo_genres_jp.loc[df_bo_genres_jp["서울 관객수"] < 100])
for g in genres_order:
df_bo_genres_jp100P[f"G_{g}"] = df_bo_genres_jp100P[f"G_{g}"] * df_bo_genres_jp100P["서울 관객수"]
df_bo_genresY_jp100P = df_bo_genres_jp100P.groupby("openYear").sum().filter(like="G_").reset_index()
axs[1].stackplot(df_bo_genresY_jp100P["openYear"],
[df_bo_genresY_jp100P["G_성인물"], df_bo_genresY_jp100P["G_드라마"], df_bo_genresY_jp100P["G_멜로/로맨스"], df_bo_genresY_jp100P["G_액션"], df_bo_genresY_jp100P["G_스릴러"], df_bo_genresY_jp100P["G_공포"], df_bo_genresY_jp100P["G_애니메이션"]],
colors=[c_ero, c_drama, c_romance, c_action, c_thriller, c_horror, c_ani], lw=1,
labels=["성인물", "드라마", "멜로/로맨스", "액션", "스릴러", "공포", "애니메이션"])
axs[1].spines[["left", "top", "right"]].set_visible(False)
axs[1].grid(False)
yticks = [0, 3000]
axs[1].set_yticks(yticks)
axs[1].set_yticklabels(yticks)
for y in yticks:
axs[1].axhline(y, c="lightgray", zorder=-1)
fig.savefig("./images/bo_genres_jp.png", dpi=200)
plot_boS_year(2011, 2020, '한국', annot_pos=0.6, xmax=5e6)
# 한국영화 장르 변화 확인 : 총 7352편
df_bo_genres_kr = df_bo_genres.query("국적 == '한국'")
print(df_bo_genres_kr.shape)
df_bo_genres_kr.head()
df_bo_genres_kr.query("G_다큐멘터리 == 1").sort_values("서울 관객수", ascending=False).head(10)
# 서울 관객 100명 이하 영화: 총 1945편
df_bo_genres_kr.loc[df_bo_genres_kr["서울 관객수"] < 100]
df_bo_genresY_kr100 = df_bo_genres_kr.loc[df_bo_genres_kr["서울 관객수"] < 100].groupby("openYear").sum().reset_index()
display(df_bo_genresY_kr100.head(3))
print(df_bo_genresY_kr100.filter(like="G_").sum().sort_values(ascending=False))
df_grade = df_bo_genres_kr.loc[df_bo_genres_kr["서울 관객수"] < 100].query("G_성인물 != 1").loc[df_bo_genres_kr["G_멜로/로맨스"]==1].query("openYear > 2000")
df_grade["영화명_국적_openYear"] = df_grade[["영화명", "국적", "openYear"]].apply(lambda x: f"{x[0]}_{x[1]}_{x[2]}", axis=1)
df_grade
df_bo_tmp = deepcopy(df_bo)
df_bo_tmp["영화명"] = df_bo_tmp["영화명"].str.replace(" ", "")
# df_bo_tmp["영화명_국적_openYear"] = df_bo[["영화명", "국적", "openYear"]].apply(lambda x: f"{x[0]}_{x[1]}_{x[2]}", axis=1)
df_grade = df_grade.merge(df_bo_tmp[["영화명", "등급"]], on="영화명")
df_grade.head()
df_grade.drop_duplicates("movieCd", inplace=True)
df_grade.shape
df_grade.head()
df_grade["등급"] = df_grade["등급"].map({"15세이상관람가":"15",
"12세이상관람가":"12",
"전체관람가":"A",
"12세관람가":"12",
"청소년관람불가":"18",
"18세관람가":"18",
"15세 미만인 자는 관람할 수 없는 등급":"15",
'12세이상관람가,12세관람가':"12",
'15세관람가,15세이상관람가':"15",
'청소년관람불가,15세이상관람가':"15",
'18세관람가,15세이상관람가':"15",
'18세관람가,청소년관람불가':"18",
'12세이상관람가,전체관람가':"A",
'12세이상관람가,15세이상관람가':"12",
'15세이상관람가,전체관람가':"A",
'제한상영가':"X",
'15세관람가,12세이상관람가':"12",
'고등학생이상관람가':"15",
'연소자관람불가':"18",
'모든 관람객이 관람할 수 있는 등급':"A",
'중학생이상관람가':"12",
'연소자관람가':"A",
'국민학생관람불가':"15",
'미성년자관람불가':"18",
'18세 미만인 자는 관람할 수 없는 등급':"18",
'12세이상관람가,연소자관람가':"A",
'연소자관람불가,15세이상관람가':"15",
'연소자관람가,15세이상관람가':"A",
'15세이상관람가,중학생이상관람가':"12",
'12세 미만인 자는 관람할 수 없는 등급':"12",
'15세 미만인 자는 관람할 수 없는 등급 ,15세이상관람가':"15",
'연소자관람불가,청소년관람불가':"18",
'연소자관람가,전체관람가':"A",
'15세이상관람가,18세 미만인 자는 관람할 수 없는 등급':"15",
'고등학생이상관람가,15세이상관람가':"15",
'15세이상관람가,미성년자관람불가':"15",
'미성년자관람가':"A",
'국민학생관람불가,15세이상관람가':"12",
'12세이상관람가,중학생이상관람가':"12",
'고등학생이상관람가,청소년관람불가':"15",
'12세이상관람가,국민학생관람불가,15세이상관람가':"12",
'12세이상관람가,국민학생관람불가':"12",
'12세이상관람가,연소자관람가,전체관람가':"A",
'청소년관람불가,12세관람가':"12",
'국민학생관람불가,중학생이상관람가':"12",
'국민학생관람불가,청소년관람불가':"12",
'청소년관람불가,전체관람가':"A",
'모든 관람객이 관람할 수 있는 등급,전체관람가':"A",
'12세이상관람가,15세 미만인 자는 관람할 수 없는 등급':"12",
'전체관람가,중학생이상관람가':"A",
'청소년관람불가,고등학생이상관람가':"15",
'12세이상관람가,고등학생이상관람가':"12"
})
print(df_grade["등급"].shape)
df_grade["등급"].value_counts(dropna=False)
df_grade = df_grade.dropna(subset=["등급"])
for g in ["A", "12", "15", "18"]:
df_grade[f"등급_{g}"] = [0] * df_grade.shape[0]
df_grade.loc[df_grade["등급"] == g, f"등급_{g}"] = 1
df_grade.drop("등급", inplace=True, axis=1)
df_grade.head()
df_grade.query("등급_18 == 0")
fig, axs = plt.subplots(figsize=(12, 6), nrows=3, sharex=True, constrained_layout=True)
# 장르별 영화 개봉 편수
axs[0].plot(df_bo_genres_kr.loc[df_bo_genres_kr["서울 관객수"] < 100].groupby("openYear").count().reset_index()["openYear"],
df_bo_genres_kr.loc[df_bo_genres_kr["서울 관객수"] < 100].groupby("openYear").count().reset_index()["서울 관객수"],
c="k", lw=3, label="개봉 영화 편수", zorder=2)
axs[0].stackplot(df_bo_genresY_kr100["openYear"],
[df_bo_genresY_kr100["G_멜로/로맨스"], df_bo_genresY_kr100["G_드라마"], df_bo_genresY_kr100["G_성인물"], df_bo_genresY_kr100["G_기타"],
df_bo_genresY_kr100["G_코미디"], df_bo_genresY_kr100["G_스릴러"], df_bo_genresY_kr100["G_다큐멘터리"]],
colors=[c_romance, c_drama, c_ero, c_etc, c_comedy, c_thriller, c_docu], lw=1,
labels=["멜로/로맨스", "드라마", "성인물", "기타", "코미디", "스릴러", "다큐멘터리"])
axs[0].set_xlim(2000, 2020)
xticks = [2000, 2005, 2010, 2015, 2020]
axs[0].set_xticks(xticks)
axs[0].set_xticklabels(xticks)
axs[0].grid(False)
yticks = [0, 300, 600]
axs[0].set_yticks(yticks)
axs[0].set_yticklabels(yticks)
for y in yticks:
axs[0].axhline(y, c="lightgray", zorder=-1)
axs[0].spines[["left", "top", "right"]].set_visible(False)
axs[0].set_title("년도별 개봉 한국 영화 수 (관객 수 100명 미만, 편)", pad=16, fontdict=font_title)
axs[0].legend(loc="upper left", fontsize="small", ncol=4)
# 관객 수
axs[1].set_title("년도별 한국 영화 관람객 수 (관객 수 100명 미만, 장르 누적, 명)", pad=16, fontdict=font_title)
df_bo_genres_kr100P = deepcopy(df_bo_genres_kr.loc[df_bo_genres_kr["서울 관객수"] < 100])
for g in genres_order:
df_bo_genres_kr100P[f"G_{g}"] = df_bo_genres_kr100P[f"G_{g}"] * df_bo_genres_kr100P["서울 관객수"]
df_bo_genresY_kr100P = df_bo_genres_kr100P.groupby("openYear").sum().filter(like="G_").reset_index()
axs[1].stackplot(df_bo_genresY_kr100P["openYear"],
[df_bo_genresY_kr100P["G_멜로/로맨스"], df_bo_genresY_kr100P["G_드라마"], df_bo_genresY_kr100P["G_성인물"], df_bo_genresY_kr100P["G_기타"],
df_bo_genresY_kr100P["G_코미디"], df_bo_genresY_kr100P["G_스릴러"], df_bo_genresY_kr100P["G_다큐멘터리"]],
colors=[c_romance, c_drama, c_ero, c_etc, c_comedy, c_thriller, c_docu], lw=1,
labels=["멜로/로맨스", "드라마", "성인물", "기타", "코미디", "스릴러", "다큐멘터리"])
axs[1].spines[["left", "top", "right"]].set_visible(False)
axs[1].grid(False)
yticks = [0, 5000]
axs[1].set_yticks(yticks)
axs[1].set_yticklabels(yticks)
for y in yticks:
axs[1].axhline(y, c="lightgray", zorder=-1)
# 등급
axs[2].set_title("년도별 한국 영화 관람 등급 (관객 수 100명 미만, 성인물 제외 멜로/로맨스, 편)", pad=16, fontdict=font_title)
df_gradeY = df_grade.groupby("openYear").sum().filter(like="등급_").reset_index()
axs[2].plot(df_bo_genresY_kr100["openYear"], df_bo_genresY_kr100["G_멜로/로맨스"], c="k", label="멜로/로맨스 개봉 편 수")
axs[2].stackplot(df_gradeY["openYear"],
[df_gradeY["등급_A"], df_gradeY["등급_12"], df_gradeY["등급_15"], df_gradeY["등급_18"]],
labels=["전체 관람가", "12세 이상 관람가", "15세 이상 관람가", "18세 이상 관람가"], lw=1, alpha=0.8)
handles, labels = axs[2].get_legend_handles_labels()
legend_romance = axs[2].legend(handles=handles[:1], labels=labels[:1], loc="upper left", fontsize="small", ncol=2)
axs[2].legend(handles=handles[1:], labels=labels[1:], loc="lower left", fontsize="small", ncol=2)
axs[2].add_artist(legend_romance)
axs[2].grid(False)
yticks = axs[0].get_yticks()
axs[2].set_yticks(yticks)
for y in yticks:
axs[2].axhline(y, c="lightgray", zorder=-1)
axs[2].set_ylim(axs[0].get_ylim())
axs[2].spines[["left", "top", "right"]].set_visible(False)
fig.savefig("./images/bo_genres_kr.png", dpi=200)
df_grade.query("등급_A == 1")
plot_boS_year(2011, 2020, '해외', annot_pos=0.6, xmax=5e6)
plot_boS_year(2011, 2020, '미국', annot_pos=0.6, xmax=5e6)
plot_boS_year(2011, 2020, '프랑스', annot_pos=0.6, xmax=5e6)
plot_boS_year(2011, 2020, '영국', annot_pos=0.6, xmax=5e6)
df_007 = df_bo.loc[df_bo["영화명"].str.contains("007") > 0].sort_values("openYear").loc[~df_bo["영화명"].str.contains("2007", na=False, case=False)]
df_007.head()
df_007.shape
fig, ax = plt.subplots(figsize=(10, 8), constrained_layout=True)
yticks = range(df_007.shape[0])
ax.set_yticks(yticks)
ax.set_yticklabels([f"{x[0]} ({x[2]})" for x in df_007[["영화명", "국적", "openYear"]].values])
xticklabels = ["영국", "미국", "한국"]
ax.set_xticks([0, 1, 2])
ax.set_xticklabels(xticklabels)
ax.set_xlim(-0.5, 2.5)
ax.set_ylim(-0.5, df_007.shape[0]-0.5)
for i, d in enumerate(df_007[["영화명", "국적", "openYear"]].values):
if d[1] == "영국":
x = 0
c = c_gb
elif d[1] == "미국":
x = 1
c = c_us
elif d[1] == "한국":
x = 2
c = c_kr
ax.scatter(x, i, lw=3, s=200, fc="w", ec=c)
ax.spines[["top", "bottom", "left", "right"]].set_visible(False)
ax.invert_yaxis()
fig.savefig("./images/007s.png", dpi=200)
# 머신러닝 모델링 데이터
df_ml = deepcopy(df_bo)
# 등급 정의
df_ml["등급"].unique()
# 등급 정리
df_ml["등급"] = df_bo["등급"].map({"15세이상관람가":"15",
"12세이상관람가":"12",
"전체관람가":"A",
"12세관람가":"12",
"청소년관람불가":"18",
"18세관람가":"18",
"15세 미만인 자는 관람할 수 없는 등급":"15",
'12세이상관람가,12세관람가':"12",
'15세관람가,15세이상관람가':"15",
'청소년관람불가,15세이상관람가':"15",
'18세관람가,15세이상관람가':"15",
'18세관람가,청소년관람불가':"18",
'12세이상관람가,전체관람가':"A",
'12세이상관람가,15세이상관람가':"12",
'15세이상관람가,전체관람가':"A",
'제한상영가':"X",
'15세관람가,12세이상관람가':"12",
'고등학생이상관람가':"15",
'연소자관람불가':"18",
'모든 관람객이 관람할 수 있는 등급':"A",
'중학생이상관람가':"12",
'연소자관람가':"A",
'국민학생관람불가':"15",
'미성년자관람불가':"18",
'18세 미만인 자는 관람할 수 없는 등급':"18",
'12세이상관람가,연소자관람가':"A",
'연소자관람불가,15세이상관람가':"15",
'연소자관람가,15세이상관람가':"A",
'15세이상관람가,중학생이상관람가':"12",
'12세 미만인 자는 관람할 수 없는 등급':"12",
'15세 미만인 자는 관람할 수 없는 등급 ,15세이상관람가':"15",
'연소자관람불가,청소년관람불가':"18",
'연소자관람가,전체관람가':"A",
'15세이상관람가,18세 미만인 자는 관람할 수 없는 등급':"15",
'고등학생이상관람가,15세이상관람가':"15",
'15세이상관람가,미성년자관람불가':"15",
'미성년자관람가':"A",
'국민학생관람불가,15세이상관람가':"12",
'12세이상관람가,중학생이상관람가':"12",
'고등학생이상관람가,청소년관람불가':"15",
'12세이상관람가,국민학생관람불가,15세이상관람가':"12",
'12세이상관람가,국민학생관람불가':"12",
'12세이상관람가,연소자관람가,전체관람가':"A",
'청소년관람불가,12세관람가':"12",
'국민학생관람불가,중학생이상관람가':"12",
'국민학생관람불가,청소년관람불가':"12",
'청소년관람불가,전체관람가':"A",
'모든 관람객이 관람할 수 있는 등급,전체관람가':"A",
'12세이상관람가,15세 미만인 자는 관람할 수 없는 등급':"12",
'전체관람가,중학생이상관람가':"A",
'청소년관람불가,고등학생이상관람가':"15",
'12세이상관람가,고등학생이상관람가':"12"
})
# 데이터 선별
df_ml = df_ml[["국적", "전국 스크린수", "전국 관객수", "서울 관객수", "장르", "등급", "openYear"]]
# 전국 관객수 10만 이상, 500만 이하
df_ml = df_ml.loc[df_ml["전국 관객수"] >= 100000].loc[df_ml["전국 관객수"] <= 5000000]
# 결측치 제거
df_ml = df_ml.dropna()
# 데이터 수
df_ml.shape
# y : 전국 관객수
y = df_ml["전국 관객수"]
# X: 나머지
X = df_ml.drop("전국 관객수", axis=1)
print(X.shape)
# LightGBM Pipeline
from sklearn.pipeline import Pipeline
from sklearn.compose import ColumnTransformer
from sklearn.preprocessing import OneHotEncoder
from lightgbm import LGBMRegressor
def model():
# categorical and numerical features
cat_features = ["국적", "장르", "등급"]
cat_transformer = OneHotEncoder(sparse=False, handle_unknown="ignore")
num_features = ["전국 스크린수", "서울 관객수", "openYear"]
num_transformer = 'passthrough'
# 1. 인자 종류별 전처리 적용
preprocessor = ColumnTransformer(transformers=[("num", num_transformer, num_features),
("cat", cat_transformer, cat_features)])
# 2. 전처리 후 XGBoost 적용
model = Pipeline(steps=[("preprocessor", preprocessor),
("ml", LGBMRegressor(use_missing=False, random_state=2021))
])
return model
# 데이터 나누기
from sklearn.model_selection import train_test_split
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=2021)
lgbm = model()
lgbm.fit(X_train, y_train)
y_train_pred = lgbm.predict(X_train)
y_test_pred = lgbm.predict(X_test)
from sklearn.metrics import r2_score
from sklearn.metrics import mean_absolute_error
from scipy import stats
# 전체데이터 예측성능
r2 = r2_score(y_test, y_test_pred)
mae = mean_absolute_error(y_test, y_test_pred)
R = stats.pearsonr(y_test, y_test_pred)
# 백만영화 예측성능
r2_m = r2_score(y_test[y_test>1e6], y_test_pred[y_test>1e6])
mae_m = mean_absolute_error(y_test[y_test>1e6], y_test_pred[y_test>1e6])
R = stats.pearsonr(y_test[y_test>1e6], y_test_pred[y_test>1e6])
fig, axs = plt.subplots(ncols=3, figsize=(15, 5), constrained_layout=True)
axs[0].scatter(df_ml["서울 관객수"]/1e6, y/1e6, s=10, alpha=0.5, c="b")
axs[1].scatter(y_train/1e6, y_train_pred/1e6, s=10, alpha=0.5, c="g", label="훈련")
axs[1].scatter(y_test/1e6, y_test_pred/1e6, s=10, alpha=0.5, c="m", label="평가")
axs[1].legend()
axs[2].scatter(y_train/1e6, (y_train_pred - y_train)/1e6, s=10, alpha=0.5, c="g")
axs[2].scatter(y_test/1e6, (y_test_pred - y_test)/1e6, s=10, alpha=0.5, c="m")
font_title = {"fontweight":"bold", "color":"0.4"}
axs[0].set_yticks([0, 1, 2, 3, 4, 5])
axs[0].set_xlabel("서울 관객 수 (백만명)", labelpad=12)
axs[0].set_ylabel("전국 관객 수 (백만명)", labelpad=12)
axs[0].set_title("서울 vs 전국 관객 수", fontdict=font_title, pad=16)
axs[1].set_xlim(0, 5)
axs[1].set_ylim(0, 5)
axs[1].set_xlabel("전국 관객 수 (실제, 백만명)", labelpad=12)
axs[1].set_ylabel("\n전국 관객 수 (예측, 백만명)", labelpad=12)
axs[1].set_yticks([0, 1, 2, 3, 4, 5])
axs[1].set_title("전국 관객 예측모델 성능 (실제 vs 예측)", fontdict=font_title, pad=16)
axs[2].axvspan(1, 5.2, fc="orange", zorder=-1, alpha=0.1)
axs[2].set_xlim(-0.2, 5.2)
axs[2].set_xticks([0, 1, 2, 3, 4, 5])
axs[2].set_ylabel("\n모델 오차 (백만명)", labelpad=12)
axs[2].set_xlabel("전국 관객 수 (백만명)", labelpad=12)
axs[2].set_title("전국 관객 예측모델 성능 (오차)", fontdict=font_title, pad=16)
axs[1].text(2.5, 1.5, " 전국 10만~50만명\n $R$ = " + f"{R[0]:.2f}\n"+"$MAE$ = " + f"{format(int(mae),',')}명", fontsize="small", va="top",
bbox={"boxstyle":"round", "pad":0.4, "facecolor":"w", "edgecolor":"gray"})
axs[2].text(3, 0.68, " 백만영화 한정\n $R$ = " + f"{R[0]:.2f}\n"+"$MAE$ = " + f"{format(int(mae_m), ',')}", fontsize="small", va="top",
bbox={"boxstyle":"round", "pad":0.4, "facecolor":"w", "edgecolor":"brown"})
fig.savefig("./images/ml.png", dpi=200)
print(mae, mae_m)
R
df_pred = deepcopy(df_bo)
df_pred["등급"] = df_bo["등급"].map({"15세이상관람가":"15",
"12세이상관람가":"12",
"전체관람가":"A",
"12세관람가":"12",
"청소년관람불가":"18",
"18세관람가":"18",
"15세 미만인 자는 관람할 수 없는 등급":"15",
'12세이상관람가,12세관람가':"12",
'15세관람가,15세이상관람가':"15",
'청소년관람불가,15세이상관람가':"15",
'18세관람가,15세이상관람가':"15",
'18세관람가,청소년관람불가':"18",
'12세이상관람가,전체관람가':"A",
'12세이상관람가,15세이상관람가':"12",
'15세이상관람가,전체관람가':"A",
'제한상영가':"X",
'15세관람가,12세이상관람가':"12",
'고등학생이상관람가':"15",
'연소자관람불가':"18",
'모든 관람객이 관람할 수 있는 등급':"A",
'중학생이상관람가':"12",
'연소자관람가':"A",
'국민학생관람불가':"15",
'미성년자관람불가':"18",
'18세 미만인 자는 관람할 수 없는 등급':"18",
'12세이상관람가,연소자관람가':"A",
'연소자관람불가,15세이상관람가':"15",
'연소자관람가,15세이상관람가':"A",
'15세이상관람가,중학생이상관람가':"12",
'12세 미만인 자는 관람할 수 없는 등급':"12",
'15세 미만인 자는 관람할 수 없는 등급 ,15세이상관람가':"15",
'연소자관람불가,청소년관람불가':"18",
'연소자관람가,전체관람가':"A",
'15세이상관람가,18세 미만인 자는 관람할 수 없는 등급':"15",
'고등학생이상관람가,15세이상관람가':"15",
'15세이상관람가,미성년자관람불가':"15",
'미성년자관람가':"A",
'국민학생관람불가,15세이상관람가':"12",
'12세이상관람가,중학생이상관람가':"12",
'고등학생이상관람가,청소년관람불가':"15",
'12세이상관람가,국민학생관람불가,15세이상관람가':"12",
'12세이상관람가,국민학생관람불가':"12",
'12세이상관람가,연소자관람가,전체관람가':"A",
'청소년관람불가,12세관람가':"12",
'국민학생관람불가,중학생이상관람가':"12",
'국민학생관람불가,청소년관람불가':"12",
'청소년관람불가,전체관람가':"A",
'모든 관람객이 관람할 수 있는 등급,전체관람가':"A",
'12세이상관람가,15세 미만인 자는 관람할 수 없는 등급':"12",
'전체관람가,중학생이상관람가':"A",
'청소년관람불가,고등학생이상관람가':"15",
'12세이상관람가,고등학생이상관람가':"12"
})
df_pred["등급"].fillna("18", inplace=True) # 등급 결측치 : 18금
df_pred["장르"].fillna("기타", inplace=True) # 장르 결측치: 기타
df_pred = df_pred.query("openYear < 2010").loc[df_pred["서울 관객수"] >= 200000].loc[df_pred["전국 관객수"] < 100]
df_pred["전국 관객수"] = lgbm.predict(df_pred[["국적", "전국 스크린수", "서울 관객수", "장르", "등급", "openYear"]])
print(df_pred.shape)
# 예측결과 확인
df_pred.loc[df_pred["전국 관객수"] >= 1000000].sort_values("전국 관객수", ascending=False).head(20)[["영화명", "감독", "개봉일", "국적", "서울 관객수", "전국 관객수"]].rename(columns={"전국 관객수":"전국 관객수 (추정)"})
# 추정치 포함 데이터베이스
df_bo_pred = deepcopy(df_bo)
df_bo_pred.loc[df_pred.index, "전국 관객수"] = df_pred["전국 관객수"]
# 영화명 띄어쓰기 제거
df_bo_pred["영화명"] = df_bo_pred["영화명"].str.replace(' ', '')
# 장르 결합 key
df_bo_pred["영화명_개봉일_대표국적"] = df_bo_pred[["영화명", "개봉일", "국적"]].apply(lambda x: f"{x[0]}_{x[1]}_{x[2]}", axis=1)
df_bo_pred.head()
# 장르 결합
df_bo_pred = df_movie_bo.merge(df_bo_pred[["영화명_개봉일_대표국적", "영화명", "개봉일", "전국 관객수", "국적", "openYear"]])
df_bo_pred = df_bo_pred.merge(df_genres.drop(["movieNm", "openYear"], axis=1), on="movieCd")
df_bo_pred = df_bo_pred.drop(["movieNm", "openDt", "repNationNm", "영화명_개봉일_대표국적"], axis=1)
print(df_bo_pred.shape)
df_bo_pred.head()
# 한국영화
df_bo_kr = df_bo_pred.query("국적 == '한국'")
df_bo_kr_m = df_bo_kr.loc[df_bo_kr["전국 관객수"] >= 1e6] # 백만영화
df_bo_kr_10m = df_bo_kr.loc[df_bo_kr["전국 관객수"] >= 1e7] # 천만영화
# 한국 백만영화
df_bo_kr_mY_0 = df_bo_kr_m.groupby("openYear")["movieCd"].count()
df_bo_kr_mY_G = df_bo_kr_m.groupby("openYear").sum().filter(like="G_")
df_bo_kr_mY = pd.concat([df_bo_kr_mY_0, df_bo_kr_mY_G], axis=1)
df_bo_kr_mY = df_bo_kr_mY.rename(columns={"movieCd":"영화편수"}).reset_index()
df_bo_kr_mY.tail()
# 한국 천만영화
df_bo_kr_10mY_0 = df_bo_kr_10m.groupby("openYear")["movieCd"].count()
df_bo_kr_10mY_G = df_bo_kr_10m.groupby("openYear").sum().filter(like="G_")
df_bo_kr_10mY = pd.concat([df_bo_kr_10mY_0, df_bo_kr_10mY_G], axis=1)
df_bo_kr_10mY = df_bo_kr_10mY.rename(columns={"movieCd":"영화편수"}).reset_index()
df_bo_kr_10mY.tail()
# 해외영화
df_bo_nkr = df_bo_pred.query("국적 != '한국'")
df_bo_nkr_m = df_bo_nkr.loc[df_bo_nkr["전국 관객수"] >= 1e6] # 백만영화
df_bo_nkr_10m = df_bo_nkr.loc[df_bo_nkr["전국 관객수"] >= 1e7] # 천만영화
# 해외 백만영화
df_bo_nkr_mY_0 = df_bo_nkr_m.groupby("openYear")["movieCd"].count()
df_bo_nkr_mY_G = df_bo_nkr_m.groupby("openYear").sum().filter(like="G_")
df_bo_nkr_mY_N = df_bo_nkr_m.groupby("openYear").sum().filter(like="N_")
df_bo_nkr_mY = pd.concat([df_bo_nkr_mY_0, df_bo_nkr_mY_G, df_bo_nkr_mY_N], axis=1)
df_bo_nkr_mY = df_bo_nkr_mY.rename(columns={"movieCd":"영화편수"}).reset_index()
df_bo_nkr_mY.tail()
# 해외 천만영화
df_bo_nkr_10mY_0 = df_bo_nkr_10m.groupby("openYear")["movieCd"].count()
df_bo_nkr_10mY_G = df_bo_nkr_10m.groupby("openYear").sum().filter(like="G_")
df_bo_nkr_10mY_N = df_bo_nkr_10m.groupby("openYear").sum().filter(like="N_")
df_bo_nkr_10mY = pd.concat([df_bo_nkr_10mY_0, df_bo_nkr_10mY_G, df_bo_nkr_10mY_N], axis=1)
df_bo_nkr_10mY = df_bo_nkr_10mY.rename(columns={"movieCd":"영화편수"}).reset_index()
df_bo_nkr_10mY.tail()
# 데이터 시각화
fig, axs = plt.subplots(ncols=3, gridspec_kw={"width_ratios":[5,1,1]},
sharey=True,
figsize=(14, 20), constrained_layout=True)
axs[0].set_title("국내 관객 백만명 이상 동원 영화 (편)", fontdict=font_title, pad=16)
axs[1].set_title("한국 영화 비율", fontdict=font_title, pad=16)
axs[2].set_title("해외 영화 비율", fontdict=font_title, pad=16)
portion_aspect0 = axs[0].get_position().height/axs[0].get_position().width
portion_aspect1 = axs[1].get_position().height/axs[1].get_position().width
portion_aspect2 = axs[2].get_position().height/axs[2].get_position().width
genres_noetc = genres_order[:20]
genres_noetc
sns.set_palette("tab20")
fig_p0, ax_p0 = plt.subplots(figsize=(axs[0].get_position().height * 20, axs[0].get_position().width * 14), constrained_layout=True)
stack_ys_kr = []
stack_ys_nkr = []
for g in genres_noetc:
stack_ys_kr.append(eval(f"-df_bo_kr_mY['G_{g}']"))
stack_ys_nkr.append(eval(f"df_bo_nkr_mY['G_{g}']"))
ax_p0.stackplot(df_bo_kr_mY["openYear"], *stack_ys_kr,
ec="w", lw=0.4, colors=c_genres[:20])
ax_p0.stackplot(df_bo_nkr_mY["openYear"], *stack_ys_nkr,
ec="w", lw=0.4, colors=c_genres[:20])
# G_기타
ax_p0.stackplot(df_bo_kr_mY["openYear"],
-df_bo_kr_mY.filter(like="G_").sum(axis=1),
ec="w", lw=0.4, zorder=-1)
ax_p0.stackplot(df_bo_nkr_mY["openYear"],
df_bo_nkr_mY.filter(like="G_").sum(axis=1),
ec="w", lw=0.4, zorder=-1)
ax_p0.get_children()[40].set_facecolor(c_etc)
ax_p0.get_children()[41].set_facecolor(c_etc)
ax_p0.set_xlim(1971, 2020)
ax_p0.set_ylim(-100, 100)
ax_p0.axis(False)
fig_p0.savefig("./images/bo_year0.png")
# 국내 genre portion
stack_ys_kr.append(-df_bo_kr_mY['G_기타'])
stack_ys_kr_p = np.array(stack_ys_kr)/np.array(stack_ys_kr).sum(axis=0)
stack_ys_kr_p = np.nan_to_num(stack_ys_kr_p)
stack_ys_kr_p.shape
fig_p1, ax_p1 = plt.subplots(figsize=(axs[1].get_position().height * 20,
axs[1].get_position().width * 14), constrained_layout=True)
ax_p1.stackplot(df_bo_kr_mY["openYear"], *stack_ys_kr_p,
ec="none", lw=0.5)
ax_p1.get_children()[20].set_facecolor(c_etc)
ax_p1.set_xlim(1971, 2020)
ax_p1.set_ylim(0, 1)
ax_p1.set_xticks([])
ax_p1.set_yticks([])
ax_p1.spines[["top", "bottom", "left", "right"]].set_visible(False)
fig_p1.savefig("./images/bo_year1.png", dpi=200)
# 해외 genre portion
stack_ys_nkr.append(-df_bo_nkr_mY['G_기타'])
stack_ys_nkr_p = np.array(stack_ys_nkr)/np.array(stack_ys_nkr).sum(axis=0)
stack_ys_nkr_p = np.nan_to_num(stack_ys_nkr_p)
stack_ys_nkr_p.shape
fig_p2, ax_p2 = plt.subplots(figsize=(axs[2].get_position().height * 20,
axs[2].get_position().width * 14), constrained_layout=True)
ax_p2.stackplot(df_bo_nkr_mY["openYear"], *stack_ys_nkr_p,
ec="none", lw=0.5)
ax_p2.get_children()[20].set_facecolor(c_etc)
ax_p2.set_xlim(1971, 2020)
ax_p2.set_ylim(0, 1)
ax_p2.set_xticks([])
ax_p2.set_yticks([])
ax_p2.spines[["top", "bottom", "left", "right"]].set_visible(False)
fig_p2.savefig("./images/bo_year2.png", dpi=200)
## 조립
# axs[0]
fig0_img = plt.imread("./images/bo_year0.png")
fig0_img = fig0_img.swapaxes(0, 1)[:,::-1, :][10:-10,10:-10,:]
x0, x1 = -100, 100
y1, y0 = 1971, 2020
axs[0].imshow(fig0_img, extent=[x0, x1, y0, y1])
axs[0].set_aspect(abs(x1-x0)/abs(y1-y0)*portion_aspect0*20/14)
axs[0].grid(False)
axs[0].set_xlim(-100, 100)
xticks = range(-100, 150, 50)
axs[0].set_xticks(xticks)
axs[0].set_xticklabels([abs(x) for x in xticks])
yticks = [1971] + list(range(1975, 2025, 5))
axs[0].set_yticks(yticks)
axs[0].set_yticklabels(yticks)
axs[0].set_title("국내 관객 백만명 이상 동원 영화 (편)", fontdict=font_title, pad=16)
axs[0].text(-50, 1971.5, f" 한국: 총 {df_bo_kr_mY['영화편수'].sum()}편 ", ha="center", va="center",
bbox={"boxstyle":"round", "pad":0.4,
"facecolor":"aliceblue", "edgecolor":"k", "linewidth":3},
zorder=20)
axs[0].text(50, 1971.5, f" 해외: 총 {df_bo_nkr_mY['영화편수'].sum()}편", ha="center", va="center",
bbox={"boxstyle":"round", "pad":0.4,
"facecolor":"0.95", "edgecolor":"k", "linewidth":3},
zorder=20)
# axs[1] 한국
fig1_img = plt.imread("./images/bo_year1.png")
fig1_img = fig1_img.swapaxes(0, 1)[:,::-1, :][10:-10,10:-10,:]
x0, x1 = 0, 1
y1, y0 = 1971, 2020
axs[1].imshow(fig1_img, extent=[x0, x1, y0, y1])
axs[1].set_aspect(abs(x1-x0)/abs(y1-y0)*portion_aspect1*20/14)
axs[1].grid(False)
xticks = [0, 1]
axs[1].set_xticks(xticks)
axs[1].set_xticklabels([0, "100%"])
axs[1].set_title("한국 영화 비율", fontdict=font_title, pad=16)
# axs[2] 해외
fig2_img = plt.imread("./images/bo_year2.png")
fig2_img = fig2_img.swapaxes(0, 1)[:,::-1, :][10:-10,10:-10,:]
x0, x1 = 0, 1
y1, y0 = 1971, 2020
axs[2].imshow(fig2_img, extent=[x0, x1, y0, y1])
axs[2].set_aspect(abs(x1-x0)/abs(y1-y0)*portion_aspect2*20/14)
axs[2].grid(False)
xticks = [0, 1]
axs[2].set_xticks(xticks)
axs[2].set_xticklabels([0, "100%"])
axs[2].set_title("해외 영화 비율", fontdict=font_title, pad=16)
display(fig)
df_kr_10m_list = df_bo_kr_10m[["영화명", "openYear", "전국 관객수"]].groupby("openYear").agg(list).reset_index()
df_kr_10m_list
df_nkr_10m_list = df_bo_nkr_10m[["영화명", "openYear", "전국 관객수"]].groupby("openYear").agg(list).reset_index()
df_nkr_10m_list
df_bo_kr_10m[["영화명", "openYear", "전국 관객수"]].sort_values("전국 관객수", ascending=False)
axs[0].scatter(0, 1975, marker="*", s=500, c="gold", ec="k", lw=2, zorder=20)
# 한국 천만영화
axs[0].text(-60, 1975, f" 한국 천만영화: 총 {df_bo_kr_10m.shape[0]}편 ", ha="center", va="center",
fontsize="medium", color="#FFFF66", fontweight="bold",
bbox={"boxstyle":"square", "pad":0.4,
"facecolor":"0.3", "edgecolor":"0.3", "linewidth":1},
zorder=20)
for y in df_kr_10m_list["openYear"].values:
m_year = df_kr_10m_list.query(f"openYear == {y}")
n_year = m_year["영화명"].apply(len).values[0]
for i in range(n_year):
axs[0].scatter(-1*(90-5*i), y, marker="*", s=200, c="gold", ec="k", lw=0.2)
for i, (_, r) in enumerate(df_bo_kr_10m[["영화명", "openYear", "전국 관객수"]].sort_values("전국 관객수", ascending=False).iterrows()):
axs[0].text(-90, 1977+i,
f"{i+1}. {r.loc['영화명']} ({r.loc['openYear']}): {format(int(r.loc['전국 관객수']), ',')} 명",
fontsize="x-small", color="gray")
# 해외 천만영화
axs[0].text(60, 1975, f" 해외 천만영화: 총 {df_bo_nkr_10m.shape[0]}편 ", ha="center", va="center",
fontsize="medium", color="#FFFF66", fontweight="bold",
bbox={"boxstyle":"square", "pad":0.4,
"facecolor":"0.3", "edgecolor":"0.3", "linewidth":1},
zorder=20)
for y in df_nkr_10m_list["openYear"].values:
m_year = df_nkr_10m_list.query(f"openYear == {y}")
n_year = m_year["영화명"].apply(len).values[0]
for i in range(n_year):
axs[0].scatter((90-5*i), y, marker="*", s=200, c="gold", ec="k", lw=0.2)
for i, (_, r) in enumerate(df_bo_nkr_10m[["영화명", "openYear", "전국 관객수"]].sort_values("전국 관객수", ascending=False).iterrows()):
axs[0].text(15, 1977+i,
f"{i+1}. {r.loc['영화명']} ({r.loc['openYear']}): {format(int(r.loc['전국 관객수']), ',')} 명",
fontsize="x-small", color="gray")
# ### legend
for i, (g, c) in enumerate(zip(genres_order, c_genres)):
axs[0].bar(0, 1, bottom=1990, label=g, fc=c, zorder=-2)
axs[0].legend(fontsize="medium", bbox_to_anchor=(0.98, 0.7), loc="upper right")
x0, x1 = -100, 100
axs[0].set_aspect(abs(x1-x0)/abs(y1-y0)*portion_aspect0*20/14)
x0, x1 = 0, 1
axs[1].set_aspect(abs(x1-x0)/abs(y1-y0)*portion_aspect1*20/14)
x0, x1 = 0, 1
axs[2].set_aspect(abs(x1-x0)/abs(y1-y0)*portion_aspect2*20/14)
fig.savefig("./images/bo_year_m.png", dpi=300)
display(fig)