《野蛮时代》运营数据分析


【数据如有侵权,请联系删除】

《野蛮时代》是一款拥有皮克斯画风的MMO策略类游戏。

在数据集有限情况下,本文展开对这款游戏数据分析,仅作为学习记录下来

import pandas as pd
import numpy as np
from matplotlib import pyplot as plt
import seaborn as sns

plt.rcParams['font.sans-serif'] = ['Simhei']
plt.rcParams['axes.unicode_minus']= False
data = pd.read_csv(r'./tap_fun_train.csv')
data.head()
user_id register_time wood_add_value wood_reduce_value stone_add_value stone_reduce_value ivory_add_value ivory_reduce_value meat_add_value meat_reduce_value ... pvp_battle_count pvp_lanch_count pvp_win_count pve_battle_count pve_lanch_count pve_win_count avg_online_minutes pay_price pay_count prediction_pay_price
0 1 2018-02-02 19:47:15 20125.0 3700.0 0.0 0.0 0.0 0.0 16375.0 2000.0 ... 0 0 0 0 0 0 0.333333 0.0 0 0.0
1 1593 2018-01-26 00:01:05 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0 0 0 0 0 0 0.333333 0.0 0 0.0
2 1594 2018-01-26 00:01:58 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0 0 0 0 0 0 1.166667 0.0 0 0.0
3 1595 2018-01-26 00:02:13 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0 0 0 0 0 0 3.166667 0.0 0 0.0
4 1596 2018-01-26 00:02:46 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0 0 0 0 0 0 2.333333 0.0 0 0.0

5 rows × 109 columns

data.info()

RangeIndex: 2288007 entries, 0 to 2288006
Columns: 109 entries, user_id to prediction_pay_price
dtypes: float64(13), int64(95), object(1)
memory usage: 1.9+ GB

确定指标

  • 关键指标:
    1.用户黏度(DAU,MAU,DAU/MAU,留存分析)
    2.用户在线(平均在线时长、每小时在线人数、每小时注册用户、最高在线人数)
    3.付费/充值(付费额、ARPU、ARPPU、付费率)
    其中:
    付费率:指定时间内,付费用户除以活跃用户数
    付费额:指定时间内,付费用户消费总额
    ARPU值:指定时间内,消费额除以活跃用户数
    ARPPU值:指定时间内,消费额处于付费用户数

  • 游戏指标:
    1.PVP、PVE、战斗力、资源(科研、建筑、人物、物品)使用等等

  • 流失定义:
    (wood_reduce_value == 0 & meat_reduce_value==0 & avg_online_minutes <=2 & pay_count ==0) 假设符合条件则定义为流失用户

  • 氪金定义:
    零氪金:pay_price = 0,
    低氪金:pay_price <=68,
    中氪金:pay_price > 68 and pay_price <= 202,
    高氪金:pay_price > 202

# 检查缺失值
data.isnull().sum()
user_id                 0
register_time           0
wood_add_value          0
wood_reduce_value       0
stone_add_value         0
                       ..
pve_win_count           0
avg_online_minutes      0
pay_price               0
pay_count               0
prediction_pay_price    0
Length: 109, dtype: int64
  • 整理数据:
    1.日期时间拆分
    2.重命名部分列名
# 新增3列
data['date']= pd.to_datetime(data['register_time'],format='%Y-%m-%d %H:%M:%S').dt.date
data['hour']= pd.to_datetime(data['register_time'],format='%Y-%m-%d %H:%M:%S').dt.hour
data['month']= pd.to_datetime(data['register_time'],format='%Y-%m-%d %H:%M:%S').dt.month
A = {'wood_add_value':'木头获取数量','stone_add_value': '石头获取数量','ivory_add_value':'象牙获取数量','meat_add_value': '肉获取数量','magic_add_value':'魔法获取数量'}
B = {'infantry_add_value':'勇士招募数量','cavalry_add_value':'驯兽师招募数量','shaman_add_value':'萨满招募数量','wound_infantry_add_value':'勇士伤兵产生数量','wound_cavalry_add_value':'驯兽师伤兵产生数量','wound_shaman_add_value':'萨满伤兵产生数量'}
C = {'general_acceleration_add_value':'通用加速卷获取数量','building_acceleration_add_value':'建筑加速卷获取数量','reaserch_acceleration_add_value':'科研加速卷获取数量','training_acceleration_add_value':'训练加速卷获取数量','treatment_acceleraion_add_value':'治疗加速卷获取数量'}
D = {'general_acceleration_reduce_value':'通用加速卷使用数量','building_acceleration_reduce_value':'建筑加速卷使用数量','reaserch_acceleration_reduce_value':'科研加速卷使用数量','training_acceleration_reduce_value':'训练加速卷使用数量','treatment_acceleration_reduce_value':'治疗加速卷使用数量'}
E = {'bd_training_hut_level':'建筑_士兵小屋等级','bd_healing_lodge_level':'建筑_治疗小井等级','bd_stronghold_level':'建筑_要塞等级','bd_outpost_portal_level':'建筑_据点传送门等级'}
F = {'sr_scout_level':'科研_侦察等级','sr_training_speed_level':'科研_训练速度等级','sr_infantry_tier_2_level':'科研_勇士等级','sr_gathering_hunter_buff_level':'科研_领土采集等级','sr_rss_help_bonus_level':'科研_资源帮助容量等级'}
G = {'pvp_battle_count':'PVP次数','pvp_lanch_count':'主动发起PVP次数','pvp_win_count':'PVP胜利次数','pve_battle_count':'PVE次数','pve_lanch_count':'主动发起PVE次数','pve_win_count':'PVE 胜利次数'}
# 替换列名
col = [A,B,C,D,E,F,G]
for co in col:
    data.rename(columns=co,inplace=True)
data.head()
user_id register_time 木头获取数量 wood_reduce_value 石头获取数量 stone_reduce_value 象牙获取数量 ivory_reduce_value 肉获取数量 meat_reduce_value ... PVE次数 主动发起PVE次数 PVE 胜利次数 avg_online_minutes pay_price pay_count prediction_pay_price date hour month
0 1 2018-02-02 19:47:15 20125.0 3700.0 0.0 0.0 0.0 0.0 16375.0 2000.0 ... 0 0 0 0.333333 0.0 0 0.0 2018-02-02 19 2
1 1593 2018-01-26 00:01:05 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0 0 0 0.333333 0.0 0 0.0 2018-01-26 0 1
2 1594 2018-01-26 00:01:58 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0 0 0 1.166667 0.0 0 0.0 2018-01-26 0 1
3 1595 2018-01-26 00:02:13 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0 0 0 3.166667 0.0 0 0.0 2018-01-26 0 1
4 1596 2018-01-26 00:02:46 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 ... 0 0 0 2.333333 0.0 0 0.0 2018-01-26 0 1

5 rows × 112 columns

运营指标分析

每日用户流失数

# 过滤定义为流失的用户
filter_leave = 'wood_reduce_value == 0 & meat_reduce_value==0 & avg_online_minutes <=2 & pay_count ==0'
leave = data.query(filter_leave)
plt.figure(figsize=(9,6))
leave_u = leave.groupby(by=['date']).user_id.agg([pd.Series.count])
plt.plot(leave_u,marker='*',color='seagreen')
# leave.groupby(by=['date']).user_id.nunique().plot(marker='*', color='r')
plt.title('每日流失用户数')
plt.xlabel('日期')
plt.ylabel('流失数量')
leave_u.describe()
count
count 40.000000
mean 15718.675000
std 13210.280473
min 7327.000000
25% 8466.750000
50% 9624.500000
75% 15179.500000
max 59637.000000

?

?

print('平均每日流失用户数与平均每日注册用户数占比:%.2f%%'%(15718/57200*100))
平均每日流失用户数与平均每日注册用户数占比:27.48%

新用户在线时长

data.avg_online_minutes.describe()
count    2.288007e+06
mean     1.020749e+01
std      3.895946e+01
min      0.000000e+00
25%      5.000000e-01
50%      1.833333e+00
75%      4.833333e+00
max      2.049667e+03
Name: avg_online_minutes, dtype: float64

在228万条数据下,新用户平均在线时长在10.2分钟,中位数在1.83分钟,最大在线时长2049分钟
假设以平均在线时长大于10.2分钟定义为活跃用户,新增一列,区分是否为活跃用户,区分条件:
非活跃用户:0<=avg_online_minutes <=10.2
活跃用户:avg_online_minutes>=10.2

  • 划分是否活跃
data['是否活跃用户'] = pd.cut(data.avg_online_minutes,bins=[0,10.2,100000],labels=[0,1],right=False)
data[['是否活跃用户']].describe()
是否活跃用户
count 2288007
unique 2
top 0
freq 1974899
print('共有活跃用户数:%.0f'% (2288007-1974899),'占比%.2f%%' % ((2288007-1974899)/2288007*100))
共有活跃用户数:313108 占比13.68%

新用户数

每日新用户数

dau = data.groupby(by=['date']).user_id.nunique()
plt.figure(figsize=(9,6))
plt.plot(dau,marker='*', color='r')
plt.title('每日新用户数')
plt.xlabel('日期')
plt.ylabel('注册数')
dau.describe()
count        40.000000
mean      57200.175000
std       16347.806529
min       36226.000000
25%       44703.250000
50%       52876.000000
75%       61699.250000
max      117311.000000
Name: user_id, dtype: float64

?

?

平均每日新用户数

avg_reg = data.groupby(by=['date']).user_id.nunique().mean()
print('平均每日新用户数:%.0f'%avg_reg)
平均每日新用户数:57200

每月新用户数

mau = data.groupby(by=['month']).user_id.nunique()
mau = mau.to_frame()
plt.figure(figsize=(9,6))
rects = plt.bar(mau.index, mau.user_id, color='c')
plt.title('每月新用户数')
plt.xlabel('月份')
plt.ylabel('注册数')
plt.xticks(np.arange(1,4), mau.index)

# 添加注释
def set_label(rects):
    for rect in rects:
        height = rect.get_height() # 获取?度
        plt.text(x = rect.get_x() + rect.get_width()/2, # ?平坐标
        y = height + 0.5, # 竖直坐标
        s = height, # ?本
        ha = 'center') # ?平居中
set_label(rects)

# 占比
mau['user_id'].map(lambda x:(x/mau['user_id'].sum())).to_frame()
user_id
month
1 0.170638
2 0.713487
3 0.115876

?

每日活跃用户平均在线时长

plt.figure(figsize=(9,6))
avg_ac_online = data[data['是否活跃用户'] == 1].groupby(by=['date']).avg_online_minutes.mean().round(2)
plt.plot(avg_ac_online,marker='*', color='g')
plt.title('每日活跃用户平均在线时长')
plt.xlabel('日期')
plt.ylabel('活跃用户在线时长')
avg_ac_online.describe().to_frame()
avg_online_minutes
count 40.000000
mean 61.353750
std 8.059266
min 46.770000
25% 56.515000
50% 61.310000
75% 65.877500
max 76.450000

?

氪金人群划分

  • 新增一列,以不同的付费额作为划分不同氪金人群
labels = ['零氪金','低氪金','中氪金','高氪金']
bins = [0,0.001,68,202,100000]
data['氪金人群'] = pd.cut(data.pay_price,bins=bins,labels=labels,right=False)

all用户与活跃用户平均在线时长

plt.figure(figsize=(9,6))
all_m = data.groupby(by=['date']).avg_online_minutes.mean().round(2)
ac_m = data[data['是否活跃用户'] == 1].groupby(by=['date']).avg_online_minutes.mean().round(2)
ac_all_m = pd.merge(all_m, ac_m, on='date')
ac_all_m.columns = ['all用户平均在线时长','活跃用户平均在线时长']
sns.lineplot(data=ac_all_m)
plt.title('all用户与活跃用户平均在线时长')
plt.xlabel('日期')
plt.ylabel('在线时长')
Text(0, 0.5, '在线时长')

?

all用户与活跃用户在不同氪金人群平均在线时长

plt.figure(figsize=(9,6))
all_m = data.groupby(by=['氪金人群']).avg_online_minutes.mean().round(2).to_frame()
all_m['用户群'] = 'all用户'
ac_m = data[data['是否活跃用户'] == 1].groupby(by=['氪金人群']).avg_online_minutes.mean().round(2).to_frame()
ac_m['用户群'] = '活跃用户'
ac_all_m = pd.concat([all_m, ac_m], axis=0)
ac_all = ac_all_m.reset_index()
ac_all.rename(columns={'avg_online_minutes':'平均在线时长'},inplace=True)
# barplot主要用来描述样本的均值和置信区间
rects = sns.barplot(data=ac_all,hue='氪金人群',x='用户群',y='平均在线时长') # 黑线默认情况则标识了95%的置信区间
plt.title('all用户与活跃用户在不同氪金人群平均在线时长')
plt.xlabel('日期')
plt.ylabel('用户类型')
Text(0, 0.5, '在线时长')

?

氪金人群数量分布

plt.figure(figsize=(9,6))
all_m = data.groupby(by=['氪金人群']).user_id.nunique().to_frame()
all_m['用户群'] = 'all用户'
ac_m = data[data['是否活跃用户'] == 1].groupby(by=['氪金人群']).user_id.nunique().to_frame()
ac_m['用户群'] = '活跃用户'
ac_all_m = pd.concat([all_m, ac_m], axis=0)
ac_all = ac_all_m.reset_index()
ac_all.rename(columns={'user_id':'用户数'},inplace=True)
rects = sns.barplot(data=ac_all,hue='氪金人群',x='用户群',y='用户数')
plt.title('all用户与活跃用户氪金人群数量分布')
plt.xlabel('用户群')
plt.ylabel('用户数')
ac_all
氪金人群 用户数 用户群
0 零氪金 2246568 all用户
1 低氪金 38707 all用户
2 中氪金 1684 all用户
3 高氪金 1048 all用户
4 零氪金 274987 活跃用户
5 低氪金 35413 活跃用户
6 中氪金 1662 活跃用户
7 高氪金 1046 活跃用户

?

氪金人群分布占比

ac_all_num = ac_all.groupby('用户群')['用户数'].sum().to_frame().reset_index()
acal = pd.merge(ac_all_num,ac_all,on='用户群')
acal.columns=['用户群','总用户数','氪金人群','用户数']
acal['氪金人群用户数与总用户数占比(%)'] = round(acal['用户数']/acal['总用户数']*100,2)
acal
用户群 总用户数 氪金人群 用户数 氪金人群用户数与总用户数占比(%)
0 all用户 2288007 零氪金 2246568 98.19
1 all用户 2288007 低氪金 38707 1.69
2 all用户 2288007 中氪金 1684 0.07
3 all用户 2288007 高氪金 1048 0.05
4 活跃用户 313108 零氪金 274987 87.82
5 活跃用户 313108 低氪金 35413 11.31
6 活跃用户 313108 中氪金 1662 0.53
7 活跃用户 313108 高氪金 1046 0.33

付费/充值

       付费率:指定时间内,付费用户除以活跃用户数
       付费额:指定时间内,付费用户消费总额
       ARPU值:指定时间内,消费额除以活跃用户数
       ARPPU值:指定时间内,消费额处于付费用户数
       LTV:用户生命周期价值

付费率

order_user_num = data[data.pay_count>0].shape[0] # 付费用户
ac_user_num = data[data['是否活跃用户'] == 1].shape[0] # 活跃用户数
print('付费率:%.2f%%' % (order_user_num/ac_user_num * 100))
付费率:13.23%

付费额

# 45日付费金额
day45_order = data[data.pay_count>0]['prediction_pay_price'].sum()
print('45日付费额:%.2f' % (day45_order))
# 未知时间付费金额
daynull_order = data[data.pay_count>0]['pay_price'].sum()
print('未知时间付费额:%.2f' % (daynull_order))
45日付费额:3916936.11
未知时间付费额:1223326.66

ARPU值

# 指定时间内,消费额除以活跃用户数
# 45日的ARPU
print('45日的ARPU:%.2f' % (day45_order/ac_user_num))
# 未知时间付费金额
print('未知时间的ARPU:%.2f' % (daynull_order/ac_user_num))
45日的ARPU:12.51
未知时间的ARP:3.91

ARPPU值

# 指定时间内,消费额处于付费用户数
# 45日的ARPU
print('45日的ARPPU:%.2f' % (day45_order/order_user_num))
# 未知时间付费金额
print('未知时间的ARPPU:%.2f' % (daynull_order/order_user_num))
45日的ARPPU:94.52
未知时间的ARPPU:29.52

游戏指标分析

氪金人群PVP平均场次

G
{'pvp_battle_count': 'PVP次数',
 'pvp_lanch_count': '主动发起PVP次数',
 'pvp_win_count': 'PVP胜利次数',
 'pve_battle_count': 'PVE次数',
 'pve_lanch_count': '主动发起PVE次数',
 'pve_win_count': 'PVE 胜利次数'}
pvp_num = pd.pivot_table(data,values=['PVP次数', '主动发起PVP次数', 'PVP胜利次数'],index=['氪金人群'], aggfunc='mean')
pvp_num.head()
PVP次数 PVP胜利次数 主动发起PVP次数
氪金人群
零氪金 1.723228 0.664597 0.760918
低氪金 22.610458 15.895342 15.069962
中氪金 54.789786 45.863420 43.035036
高氪金 73.050573 62.517176 56.512405
plt.figure(figsize=(9,6))
sns.lineplot(data=pvp_num, marker='*',sizes=20)
plt.title('氪金人群PVP平均场次')
plt.xlabel('氪金人群')
plt.ylabel('PVP次数')
Text(0, 0.5, 'PVP次数')

?

list(zip(range(len(pvp_num)),pvp_num['PVP次数']))
[(0, 1.723228497868749),
 (1, 22.610458056682255),
 (2, 54.78978622327791),
 (3, 73.05057251908397)]
pvp_num.plot(kind='bar', stacked='True',figsize=(10,8),colormap='tab10')
for a,b in zip(range(len(pvp_num)),pvp_num['PVP次数']):
    plt.text(a,b/2,f'{b:.2f}', ha='center', va='bottom', size=12)
plt.title('氪金人群PVP平均场次')
plt.xlabel('氪金人群')
plt.ylabel('PVP次数')
Text(0, 0.5, 'PVP次数')

?

氪金人群PVE平均场次

pvp_num = pd.pivot_table(data,values=['PVE次数', '主动发起PVE次数', 'PVE 胜利次数'],index=['氪金人群'], aggfunc='mean')
plt.figure(figsize=(9,6))
sns.lineplot(data=pvp_num, marker='*',sizes=20)
plt.title('氪金人群PVE平均场次')
plt.xlabel('氪金人群')
plt.ylabel('PVE次数')
pvp_num
PVE 胜利次数 PVE次数 主动发起PVE次数
氪金人群
零氪金 1.914146 2.141877 2.133394
低氪金 34.833260 38.167592 37.968300
中氪金 68.611639 74.910926 74.472090
高氪金 81.838740 89.126908 88.460878

?

氪金人群人均物品数

A
{'wood_add_value': '木头获取数量',
 'stone_add_value': '石头获取数量',
 'ivory_add_value': '象牙获取数量',
 'meat_add_value': '肉获取数量',
 'magic_add_value': '魔法获取数量'}
pvp_num = pd.pivot_table(data,values=A.values(),index=['氪金人群'], aggfunc='mean')
plt.figure(figsize=(9,6))
sns.lineplot(data=pvp_num, marker='*',sizes=20)
plt.title('氪金人群人均物品数')
plt.xlabel('氪金人群')
plt.ylabel('物品数')
Text(0, 0.5, '物品数')

?

氪金人群人均人物数

B
{'infantry_add_value': '勇士招募数量',
 'cavalry_add_value': '驯兽师招募数量',
 'shaman_add_value': '萨满招募数量',
 'wound_infantry_add_value': '勇士伤兵产生数量',
 'wound_cavalry_add_value': '驯兽师伤兵产生数量',
 'wound_shaman_add_value': '萨满伤兵产生数量'}
pvp_num = pd.pivot_table(data,values=B.values(),index=['氪金人群'], aggfunc='mean')
plt.figure(figsize=(9,6))
sns.lineplot(data=pvp_num, marker='*',sizes=20)
plt.title('氪金人群人均人物数')
plt.xlabel('氪金人群')
plt.ylabel('人物数')
Text(0, 0.5, '人物数')

?

氪金人群人均功能数

氪金人群人均加速卷获取情况

C
{'general_acceleration_add_value': '通用加速卷获取数量',
 'building_acceleration_add_value': '建筑加速卷获取数量',
 'reaserch_acceleration_add_value': '科研加速卷获取数量',
 'training_acceleration_add_value': '训练加速卷获取数量',
 'treatment_acceleraion_add_value': '治疗加速卷获取数量'}
pvp_num = pd.pivot_table(data,values=C.values(),index=['氪金人群'], aggfunc='mean')
pvp_num
plt.figure(figsize=(9,6))
sns.lineplot(data=pvp_num, marker='*',sizes=20)
plt.title('氪金人群人均加速卷获取情况')
plt.xlabel('氪金人群')
plt.ylabel('加速卷获取数')
Text(0, 0.5, '加速卷获取数')

?

氪金人群人均加速卷使用情况

D
{'general_acceleration_reduce_value': '通用加速卷使用数量',
 'building_acceleration_reduce_value': '建筑加速卷使用数量',
 'reaserch_acceleration_reduce_value': '科研加速卷使用数量',
 'training_acceleration_reduce_value': '训练加速卷使用数量',
 'treatment_acceleration_reduce_value': '治疗加速卷使用数量'}
pvp_num = pd.pivot_table(data,values=D.values(),index=['氪金人群'], aggfunc='mean')
plt.figure(figsize=(9,6))
sns.lineplot(data=pvp_num, marker='*',sizes=20)
plt.title('氪金人群人均加速卷使用情况')
plt.xlabel('氪金人群')
plt.ylabel('加速卷数')
Text(0, 0.5, '加速卷数')

?

氪金人群人均建筑数

E
{'bd_training_hut_level': '建筑_士兵小屋等级',
 'bd_healing_lodge_level': '建筑_治疗小井等级',
 'bd_stronghold_level': '建筑_要塞等级',
 'bd_outpost_portal_level': '建筑_据点传送门等级'}
pvp_num = pd.pivot_table(data,values=E.values(),index=['氪金人群'], aggfunc='mean')
plt.figure(figsize=(9,6))
sns.lineplot(data=pvp_num, marker='*',sizes=20)
plt.title('氪金人群人均建筑数')
plt.xlabel('氪金人群')
plt.ylabel('建筑数')
Text(0, 0.5, '建筑数')

?

氪金人均科研等级

F
{'sr_scout_level': '科研_侦察等级',
 'sr_training_speed_level': '科研_训练速度等级',
 'sr_infantry_tier_2_level': '科研_勇士等级',
 'sr_gathering_hunter_buff_level': '科研_领土采集等级',
 'sr_rss_help_bonus_level': '科研_资源帮助容量等级'}
pvp_num = pd.pivot_table(data,values=F.values(),index=['氪金人群'], aggfunc='mean')
plt.figure(figsize=(9,6))
sns.lineplot(data=pvp_num, marker='*',sizes=20)
plt.title('氪金人均科研等级')
plt.xlabel('氪金人群')
plt.ylabel('科研等级')
Text(0, 0.5, '科研等级')

?