Prologue
게임 데이터를 데이터 EDA하고, 프로젝트 목표와 대시보드 목표 대시보드 레이아웃을 구성한 과정을 설명하고자 한다.
데이터 EDA
결측값 확인
display(보여줄 것, 보여줄 것2)
: 화면에 보여줄 것들을 표시한다. 데이터프레임이 표 형태로 보여져서 가독성이 좋다.
데이터 타입 확인
가장 아래 데이터 타입별로 몇 개의 열이 있는지 확인할 수 있다. object type
은 11개.
판다스에 object type은 문자열 혹은 문자와 숫자로 이루어진 데이터 유형이다.
판다스의 데이터 종류
category
는 value의 개수가 몇 개 없을 때 사용할 수 있는 데이터 타입이다.
가령 성별, 나이(10대, 20대 ..) 등의 데이터에 사용하면 된다.
한글 폰트 적용
기본 matplotlib의 시각화에 한글이 적용되지 않는다.
한글 폰트를 지정하고 -(음수 부호)를 유니코드가 아닌 ASCII 코드로 표현하게 해서
한글로 시각화가 가능하게 한다.
# 한글 폰트 적용
import matplotlib.font_manager as fm
plt.rcParams['font.family'] ='Malgun Gothic' #윈도우는 맑은 고딕, 애플은 "AppleGothic"
plt.rcParams['axes.unicode_minus'] =False
matplotlib 시각화 복습
np.random.rand(1000)
: 0과 1사이의 실수를 골고루 뽑는다.
np.random.randn(1000)
: -3과 3사이의 실수를 평균 0, 표준편차 1을 따르는 정규분포에 따라 난수 생성
구입 카테고리별 결제금액
# 가장 많이 결제를 발생시킨 카테고리는? ->구입 카테고리별 결제금액
category_cnt = df['main_purchase_category'].value_counts().sort_values(ascending=False)
# 원래 숫자로 표기
import matplotlib.ticker as mticker
# 천 단위 콤마를 추가하는 포맷터 함수
def comma_formatter(x, pos):
return f'{int(x):,}' # 1000000000 → 1,000,000,000
# 카테고리별 총 결제금액
category_purchase = df.groupby('main_purchase_category')['pay_amont'].sum().sort_values(ascending=False)
# 시각화
fig, ax1 = plt.subplots(figsize=(10, 5))
color = 'tab:blue'
ax1.set_xlabel('카테고리')
ax1.set_ylabel('결제금액', color=color)
ax1.bar(category_purchase.index, category_purchase.values, color=color, alpha=0.6, label='결제금액')
ax1.tick_params(axis='y', labelcolor=color)
ax1.set_ylim(0,450000000000)
ax1.legend(loc='upper right')
# y축에 지수 표기 대신 전체 숫자 표시
ax1.yaxis.set_major_formatter(mticker.ScalarFormatter()) # <-- 지수 표기 비활성화
ax1.ticklabel_format(style='plain', axis='y') # <-- y축에 지수 표기 안 쓰도록 설정
# 천 단위로 , 표시
ax1.yaxis.set_major_formatter(mticker.FuncFormatter(comma_formatter))
ax2 = ax1.twinx() # 두 번째 y축 생성
color = 'tab:red'
ax2.set_ylabel('메인 구매 카테고리 수', color=color)
ax2.plot(category_cnt.index, category_cnt.values, label='메인 구매 카테고리 수', marker='o', color=color, linestyle='--')
ax2.tick_params(axis='y', labelcolor=color)
ax2.set_ylim(0,10000)
ax2.legend(loc='upper right', bbox_to_anchor=(1, 0.9))
plt.title('구매 카테고리별 총 결제금액과 메인 구매 카테고리 수')
plt.grid()
plt.show()
display(category_cnt, category_purchase)
- 가장 많이 결제를 발생시킨 활동 카테고리는 3가지는 무기강화패키지, 뽑기, 레벨업입니다.
- 복귀유저를 늘린다면 복귀유저패키지를 통해 결제금액을 높일 수 있습니다.
- 매력적인 디자인으로 코스튬과 장신구를 만들면 코스튬, 장신구 패키지를 통해 결제금액을 높일 수 있습니다.
나라별 결제금액
# 가장 많은 금액이 발생한 지역은? -> 나라별 결제금액
# 지역별 총 결제금액
country_purchase = df.groupby('country')['pay_amont'].sum().sort_values(ascending=False)
# 시각화 - 주요 활동별 평균 플레이타임 & 결제금액 비교
fig, ax1 = plt.subplots(figsize=(10, 5))
color = 'tab:blue'
ax1.set_xlabel('지역')
ax1.set_ylabel('결제금액', color=color)
ax1.bar(country_purchase.index, country_purchase.values, color=color, alpha=0.6, label='총 결제금액')
ax1.tick_params(axis='y', labelcolor=color)
ax1.legend()
# y축에 지수 표기 대신 전체 숫자 표시
ax1.yaxis.set_major_formatter(mticker.ScalarFormatter()) # <-- 지수 표기 비활성화
ax1.ticklabel_format(style='plain', axis='y') # <-- y축에 지수 표기 안 쓰도록 설정
# 천 단위로 , 표시
ax1.yaxis.set_major_formatter(mticker.FuncFormatter(comma_formatter))
plt.title('나라별 총 결제금액')
plt.show()
# 원자료 표시
display(country_purchase, df['country'].value_counts().sort_values(ascending=False))
- 한국, 중국을 포함한 아시아권의 결제금액이 높았습니다.
- 총 31개 국가 중 KR-CH까지 15개 국가는 매출이 발생하나 그 외 16개 국가는 유저 수가 적고 매출이 미미합니다. # len(df['country'].value_counts()) 로 확인
- AT부터 CH 13개 국가는 매출을 늘릴 수 있는 가능성이 높습니다. 각 국가의 유저 수를 늘리면 매출을 발생시킬 수 있습니다.
평가점수 구간별 구매금액과 환불금액
# 서비스 평가 점수 구간별 누적환불금액
# 구간 정의 (0-1, 1-2, 2-3, 3-4, 4-5)
bins = [0, 1, 2, 3, 4, 5]
# 구간 레이블 지정
labels = ['0-1', '1-2', '2-3', '3-4', '4-5']
# 점수를 구간별로 나누기
df['score_group'] = pd.cut(df['review_score'], bins=bins, labels=labels, right=True, include_lowest=True)
df.head(3)
# 구간별로 그룹화해서 결제금액 확인
pay_score_group = df.groupby('score_group')['pay_amont'].sum().sort_values(ascending=False)
# 시각화 - 주요 활동별 평균 플레이타임 & 결제금액 비교
fig, ax1 = plt.subplots(figsize=(10, 5))
color = 'tab:blue'
ax1.set_xlabel('점수구간')
ax1.set_ylabel('결제금액', color=color)
ax1.bar(pay_score_group.index, pay_score_group.values, color=color, alpha=0.6, label='결제금액')
ax1.tick_params(axis='y', labelcolor=color)
ax1.legend()
# y축에 지수 표기 대신 전체 숫자 표시
ax1.yaxis.set_major_formatter(mticker.ScalarFormatter()) # <-- 지수 표기 비활성화
ax1.ticklabel_format(style='plain', axis='y') # <-- y축에 지수 표기 안 쓰도록 설정
# 천 단위로 , 표시
ax1.yaxis.set_major_formatter(mticker.FuncFormatter(comma_formatter))
plt.title('평가점수 구간별 결제금액')
plt.show()
# 구간 별 유저 수 표시
display(df['score_group'].value_counts())
- 당연하게도..? 리뷰점수가 높을수록 결제금액이 많았습니다!
리뷰점수 구간별 환불금액
# 구간별로 그룹화해서 환불금액 확인
refund_score_group = df.groupby('score_group')['refund_amount'].sum().sort_values(ascending=False)
# 시각화 - 주요 활동별 평균 플레이타임 & 결제금액 비교
fig, ax1 = plt.subplots(figsize=(10, 5))
color = 'tab:blue'
ax1.set_xlabel('점수구간')
ax1.set_ylabel('환불금액', color=color)
ax1.bar(refund_score_group.index, refund_score_group.values, color=color, alpha=0.6, label='환불금액')
ax1.tick_params(axis='y', labelcolor=color)
ax1.legend()
# y축에 지수 표기 대신 전체 숫자 표시
ax1.yaxis.set_major_formatter(mticker.ScalarFormatter()) # <-- 지수 표기 비활성화
ax1.ticklabel_format(style='plain', axis='y') # <-- y축에 지수 표기 안 쓰도록 설정
# 천 단위로 , 표시
ax1.yaxis.set_major_formatter(mticker.FuncFormatter(comma_formatter))
plt.title('평가점수 구간별 환불금액')
- 환불금액은 매출에 영향이 거의 없습니다.
- 환불금액은 전체금액의 0.09%입니다. # df['refund_amount'].sum() / df['pay_amont'].sum() 로 확인
- 총 환불금액 = 1,227,479,032원/ 총 결제금액 = 1,356,246,770,820원
- 2-3점, 3-4점, 4-5점 순서로 환불금액이 높았습니다.
데이터를 통해 게임 유추추
df.head(2)
# 1. 모바일게임이다. 리뷰점수가 2.28~5점 사이에 있다. OS가 ios와 android로 나뉜다.
display(df['review_score'].max(),df['review_score'].min())
display(df['os'].value_counts())
# 2. 오픈베타 시작? 혹은 게임 출시 날짜가 2017.04.02이다. 최초 로그인 = 게임의 시작으로 본다면
# 2017년 4월 출시 모바일 rpg 게임
display(df['first_login_date'].min(), df['first_login_date'].max())
display(df['last_login_date'].min(), df['last_login_date'].max())
display(df[df['first_login_date'] == '2017-04-02 00:00:00']['country'].count(),
df[df['first_login_date'] == '2017-04-02 00:00:00']['country'].value_counts())
# 3. 배틀패스(진척도를 올려 각종 치장 및 게임 화폐 보상 등을 얻을 수 있는 시스템)를 통해 보상을 얻을 수 있다.
display(df['battle_pass_level'].min(), df['battle_pass_level'].max())
- 배틀패스 참고 링크: [배틀패스, 나무위키](https://namu.wiki/w/%EB%B0%B0%ED%8B%80%20%ED%8C%A8%EC%8A%A4)
# 4. 인스턴스 던전이 있다.
display(df['instance_Dungeon_enter_cnt'].min(), df['instance_Dungeon_enter_cnt'].max())
- 인스턴스 던전(인던) 참고 링크: [인던과 던전의 차이, 서울와이어](https://www.seoulwire.com/news/articleView.html?idxno=53771)
# 5. 결제금액 1,356,246,770,820원 = 1조 3천억
display(df['pay_amont'].sum())
df.info()
# 만렙을 통한 확인
display(df['level'].min(), df['level'].max())
리니지m 만렙을 모르는 유저들..?
출처: [리니지m 커뮤니티](https://lineagem.plaync.com/board/free/view?articleId=59496a2cd54e640001f90678)
# 게임 직업의 종류:
df['job'].value_counts()
리니지m에는 직업의 종류가 엄청 다양함(13개)

# 한국 유저 수 7600명 정도..
df.loc[df['country']=='KR', 'country'].count()
plt.hist(data=df, x='last_login_date')
최종 대시보드 구성
현재 상황
신규 고객이 2018년 10월을 기점으로 끊김
이탈 고객은 매 달 약 1500명 발생함.
게임 폐쇄 위기: 2024년 이후 접속한 유저는 12%
프로젝트 목표
따라서 전사는 이탈률을 낮추고 신규 고객 수를 늘리는 것에 집중하기로 했다.
경영진: 잔존하는 고객, 신규 고객으로부터 매출 발생시키기
영업진: 잔존율 높이고 신규 고객 유치
어떻게? 대시보드를 통해 이탈이 발생하는 파이프라인을 확인하고 잔존율을 높이자
대시보드 기획
유저 대시보드는 현재 이탈 유저와 잔존 유저의 수를 확인하고
어떻게 이탈률이 결정되는지 시각화한다.
매출 대시보드는 이탈률에 따라 매출이 어떻게 변동하는지,
현재 이탈 의심유저에 따른 손실예상금액 등의 위험성을 나타내서
손실을 최소화할 수 있는 행동을 하게 한다.
'Today I Learned' 카테고리의 다른 글
[TIL] 25.02.19 태블로로 게임 매출 대시보드 만들기 (0) | 2025.02.20 |
---|---|
[TIL] 25.02.18 태블로로 게임 매출 대시보드 만들기 (1) | 2025.02.20 |
25.02.16 Olist데이터 SQL 쿼리 (0) | 2025.02.18 |
[TIL] 25.02.14 태블로 프로젝트 1일차 (0) | 2025.02.14 |
[TIL] 25.02.12 A/B 테스트 결과 태블로로 시각화 (0) | 2025.02.13 |