美文网首页简书の个人活动⛺️拾光书屋
用二十万条数据解读简书抽奖逻辑

用二十万条数据解读简书抽奖逻辑

作者: 初心不变_叶子 | 来源:发表于2022-08-08 14:45 被阅读0次

最近在社区看了看,好多小伙伴都对简书抽奖相关的事情感兴趣,这次我们用数据探索一下。

数据集

这次的数据来源是抽奖页面最下方的中奖名单,这玩意:

如果大家仔细观察过的话,中奖名单中的信息都有一个相同的条件:奖项大于“收益加成卡 100”。

这个名单的数据来源是简书的一个接口,于是我写了一点代码,每天自动保存新增的中奖数据。

然后,把这个采集脚本放到服务器上,跑它几个月。

前几天一看,数据量快达到二十五万了,索性就拿出来做下分析。

本次使用的是简书抽奖数据,包含 2021.12.29 到 2022.08.05 共 219 天中,所有奖项高于“收益加成卡 100”的抽奖记录。

数据共有 241755 条,存储在 MongoDB 中,占用空间 7.79MB。

数据结构如下:

{
  "_id": 23812870,
  "time": {
    "$date": {
      "$numberLong": "1640782059000"
    }
  },
  "reward_name": "收益加成卡100",
  "user": {
    "id": 24124389,
    "url": "https://www.jianshu.com/u/2f7a3e46c654",
    "name": "薇叶儿"
  }
}

数据的导入与预处理

初始化数据库连接,分批次导入数据,转换为 DataFrame:

df = pd.DataFrame()

temp = []
for item in db.find({}):
    item["user_id"] = item["user"]["id"]
    item["user_url"] = item["user"]["url"]
    item["user_name"] = item["user"]["name"]
    del item["user"]

    temp.append(item)

    if len(temp) == 1000:
        df = df.append(temp)
        temp.clear()

del temp

这里需要注意,由于 MongoDB 是文档型数据库,导出的格式是 JSON,包含嵌套关系,但便于我们分析的 DataFrame 格式应该是扁平的,因此我们需要对数据进行展开。

这里需要展开的字段较少,可以手动进行,在数据字段较多的情况下,可以考虑写一个工具函数实现展开,或使用 MongoDB 聚合功能实现展开,后者实现难度略高,但性能更好。

查看部分数据:

df.head()

查看数据类型:

df.info()
<class 'pandas.core.frame.DataFrame'>
Int64Index: 241000 entries, 0 to 999
Data columns (total 6 columns):
 #   Column       Non-Null Count   Dtype
---  ------       --------------   -----
 0   _id          241000 non-null  int64
 1   time         241000 non-null  datetime64[ns]
 2   reward_name  241000 non-null  object
 3   user_id      241000 non-null  int64
 4   user_url     241000 non-null  object
 5   user_name    241000 non-null  object
dtypes: datetime64[ns](1), int64(2), object(3)
memory usage: 12.9+ MB

其实这里可以发现,_id 列和 user_id 列的数据没有必要使用 int64 存储,存在内存浪费,但这份数据并不算很大,占用的内存空间不会影响我们的分析,这里无需处理。

使用以下代码查看 DataFrame 占用的内存空间:

print(df.memory_usage(deep=True).sum() / 1024 / 1024, "MB")
72.4241714477539 MB

什么时候中奖的人数最多?

先对数据进行简单的按月聚合,获得每月的中奖人次:

grouper = pd.Grouper(key="time", freq="M")
month_df = df.groupby(grouper)["_id"].count().reset_index()

month_df.rename(columns={"time": "month", "_id": "count"}, inplace=True)
month_df["月份"] = month_df["month"].apply(lambda x: x.month)
month_df.drop([0, 8], inplace=True)

month_df

开头和结尾采集的数据不完整,这里将其删去,结果如下:

month count
1 33056
2 29944
3 35346
4 33758
5 34688
6 32368
7 34554

直观一些,我们来画个图:

(
    Line()
    .add_xaxis(list(month_df["month"]))
    .add_yaxis("中奖人数", list(month_df["count"]))
    .set_global_opts(
        title_opts=opts.TitleOpts(title="月份与中奖人数关系")
    )
    .render_notebook()
)

由此可见,在这段时间内,中奖率并没有发生显著变化。

哪些奖项出现的最多?

type_df = df.groupby("reward_name")["_id"].count().reset_index()
type_df.rename(columns={"_id": "中奖人数"}, inplace=True)
type_df.sort_values("中奖人数", ascending=False, inplace=True)
type_df["综合中奖率"] = type_df["中奖人数"].apply(lambda x: f"{round(x / 241755 * 100, 3)}%")
type_df
reward_name 中奖人数 综合中奖率
收益加成卡100 231776 95.872%
收益加成卡1万 9112 3.769%
四叶草徽章 104 0.043%
锦鲤头像框1年 8 0.003%

到这里,事情变得有意思了起来。

不出所料,100 加成卡以绝对优势位居榜首,综合中奖率在 95% 以上。

10000 加成卡的中奖率也尚可接受,高于 3%。

四叶草徽章的中奖率低了点啊,在简书取消徽章合成玩法后,这个徽章已经失去了用途,变成了装饰品,一个装饰品居然拥有比能带来实际收益的物品(10000 加成卡)更低的中奖率,简书紧握着四叶草徽章不放,是单纯不想更改概率,还是这个徽章将被开发出新的用途?

锦鲤头像框的中奖率更低,半年中只有 8 位简友中奖,这里放上他们的个人主页链接,大家可以去看看他们是否佩戴了头像框:

飞鸿雪舞
李本意小姐姐
c3e6b92fe89c
陌爻凉
消消乐的日常
eggplant1223
暮沉误
漠北兄弟

眼尖的简友们可能已经发现了,还有两种奖励没有出现过,它们是:

  • 免费开 1 次连载
  • 招财猫头像框 1 年

关于免费开连载这一奖励,我听说社区中有小伙伴中过奖,可能这个接口本身就不会返回这个奖项的中奖人,所以无法被采集到。

至于招财猫头像框,我从来没在社区看到过,有看到的小伙伴麻烦评论区指个路,谢谢啦~

大家喜欢在什么时候抽奖?

这一问题的研究基于一个假设:中奖率不随时间而变化。

首先我们来看一天中的抽奖人数分布:

temp_df = df.copy()
temp_df["hour"] = temp_df["time"].apply(lambda x: x.hour)
hour_df = temp_df.groupby("hour")["_id"].count().reset_index()
del temp_df

(
    Bar()
    .add_xaxis(list(hour_df["hour"]))
    .add_yaxis("抽奖人数", list(hour_df["_id"]))
    .set_global_opts(
        title_opts=opts.TitleOpts(title="抽奖人数分布(小时)")
    )
    .render_notebook()
)

早晚抽奖的人数多于中午,深夜抽奖人数最少。

然后是一周中的人数分布:

temp_df = df.copy()
temp_df["week"] = temp_df["time"].apply(lambda x: x.weekday())
week_df = temp_df.groupby("week")["_id"].count().reset_index()
del temp_df

(
    Bar()
    .add_xaxis(list(week_df["week"]))
    .add_yaxis("抽奖人数", list(week_df["_id"]))
    .set_global_opts(
        title_opts=opts.TitleOpts(title="抽奖人数分布(周)")
    )
    .render_notebook()
)

没有明显起伏,这说明简书的活跃用户量基本不随星期而改变。

经常抽奖的是新用户还是老用户?

众所周知,简书的用户 ID 是有序的,越早注册的用户,UID 越小。

在这份数据中,UID 最小,也就是注册时间最早的用户是 alue,时间是 2022 年 6 月 17 日。

他算是简书的元老级用户,主页最新文章的发布时间是昨天,看信息应该是一名全栈工程师。

(还可能是 Wolai 用户?)

UID 最大,注册时间最晚的用户是 简悦58,时间是 2022 年 8 月 5 日。

她的账号也是在这一天注册的,早晨注册,下午尝试抽奖,并且抽中了简书给她的第一张 100 加成卡。

接下来,我们将所有用户按照 UID 分组,间隔为一百万,并画出柱状图:

新用户参与抽奖的频率更高,或者说现在在简书活跃的大多是新用户。

这是否意味着大量的用户流失?

谁是欧皇?

要解决这个问题,我们需要比较准确地衡量出奖品的价值。

前面已经给出了各奖品的中奖率,我们将中奖率取倒数,就获得了(至少在设计者看来)的奖品价值:

(为了提升精确度,此处中奖百分比保留 7 位有效数字,转换成小数就是 9 位精度)

temp_dict = {
    "收益加成卡100": 0.958722674,
    "收益加成卡1万": 0.037691051,
    "四叶草徽章": 0.000430188,
    "锦鲤头像框1年": 0.000033091
}

reward_to_value = {key: round(1 / value, 3)
                   for key, value in temp_dict.items()
}

for key, value in reward_to_value.items():
    print(f"{key} {value}")
奖品 价值
收益加成卡100 1
收益加成卡1万 26
四叶草徽章 2324
锦鲤头像框1年 30219

这里发现 100 加成卡和 10000 加成卡的价值比是 1:26,而两者的绝对价值比为 1:100,这一设计使抽到 10000 加成卡的利益远大于 100 加成卡,因此带动了抽奖机会的获取,同时提升了广告曝光量。

按照铜牌会员的加成卡获取量折算,四叶草徽章(永久)价值 103.80 元,锦鲤头像框(1 年)价值 1349.73 元。

如果按照白金会员折算,四叶草徽章(永久)价值 79.56 元,锦鲤头像框(1 年)价值 1034.58 元。

如果简书想要卖这两个徽章,也许可以参考这两个价格作为用户心理预期。

不过锦鲤卖到这个价格真的会有人买吗?

接下来,根据奖品价值计算每个用户的获奖总价值:

user_id_list = [x for x in db.distinct("user.id")]

def job(user_id, data):
    item_reward_data = {key: 0 for key in reward_to_value.keys()}
    reward_value = 0

    for item in data:
        item_reward_data[item["reward_name"]] += 1
        reward_value += reward_to_value[item["reward_name"]]

    return (user_id, item_reward_data, reward_value)

futures = []
with ThreadPoolExecutor(max_workers=8) as pool:
    for user_id in user_id_list:
        data = db.find({"user.id": user_id})
        future = pool.submit(job, user_id, data)
        futures.append(future)

    pool.shutdown(wait=True)
user_reward_value = []

for future in futures:
    user_id, item_reward_data, reward_value = future.result()
    user_data = db.find_one({"user.id": user_id})["user"]

    user_reward_value.append({
        "user": user_data,
        "reward_data": item_reward_data,
        "reward_value": reward_value
    })

del futures

(这里不使用线程池也能获得可接受的运行时间,当时我忘了给数据库建索引,误以为存在性能问题,所以做了多线程优化)

获取最幸运的用户:

max_reward_user = {"reward_value": 0}

for user in user_reward_value:
    if user["reward_value"] > max_reward_user["reward_value"]:
        max_reward_user = user

print(max_reward_user)

最幸运的用户是 c3e6b92fe89c,她的奖品总价值为 33107,获奖明细如下:

奖品名称 获奖次数
收益加成卡100 330
收益加成卡1万 9
四叶草徽章 1
锦鲤头像框1年 1

抽到最多次 10000 加成卡的用户:

max_10000_user = {"reward_data": {"收益加成卡1万": 0}}

for user in user_reward_value:
    if user["reward_data"]["收益加成卡1万"] > max_10000_user["reward_data"]["收益加成卡1万"]:
        max_10000_user = user

print(max_10000_user)

抽到最多次 10000 加成卡的用户是 舜真如心,她在没有抽到过四叶草徽章和锦鲤头像框的情况下,获得了价值 904 的奖品。

抽到最多次 100 加成卡的用户是 王别二,404 张 100 加成卡,平均一天中 2 个,想必是抽奖常客吧。

100 加成卡获取数量分布如下:

10000 加成卡获取数量分布如下:

最后,来统计一下大家的奖品价值情况:

(为了避免高价值奖品获得者将图像顶起影响分析,本图表不考虑四叶草徽章和锦鲤头像框的价值)

for user in user_reward_value:
    if user["reward_data"]["四叶草徽章"] != 0:
        user["reward_value"] -= reward_to_value["四叶草徽章"] * user["reward_data"]["四叶草徽章"]
    if user["reward_data"]["锦鲤头像框1年"] != 0:
        user["reward_value"] -= reward_to_value["锦鲤头像框1年"] * user["reward_data"]["锦鲤头像框1年"]
temp_dict = {}

for user in user_reward_value:
    group = int(user["reward_value"] / 10)
    if temp_dict.get(group):
        temp_dict[group] += 1
    else:
        temp_dict[group] = 1

total_dict = {}
for i in range(max(temp_dict.keys())):
    if not temp_dict.get(i):
        total_dict[i] = 0
    else:
        total_dict[i] = temp_dict[i]

del temp_dict
x = list(total_dict.keys())
x.sort()
y = list(total_dict.values())

(
    Bar()
    .add_xaxis(x)
    .add_yaxis("人数", y, category_gap=0)
    .set_global_opts(
        title_opts=opts.TitleOpts(title="奖品价值分布(每 10 价值为一组)"),
    )
    .set_series_opts(
        label_opts=opts.LabelOpts(is_show=False)
    )
    .render_notebook()
)

所以,抽奖是一件需要长期积累的事情,有时也需要一点运气。

以上是本次数据分析的全部内容,希望对大家有所帮助。

相关文章

  • 用二十万条数据解读简书抽奖逻辑

    最近在社区看了看,好多小伙伴都对简书抽奖相关的事情感兴趣,这次我们用数据探索一下。 数据集 这次的数据来源是抽奖页...

  • 抽奖轮盘去哪了

    这几天,不断有简友说抽奖轮盘找不到了,不能抽奖了! 这是什么情况? 上周我用苹果手机登录了简书,发现的确没有抽奖轮...

  • 简书.10万数据分析教你如何上首页投稿

    数据来源:简书.首页投稿 专题 数据时间:2016.1.10-2016.5.10 数据量:超10万条文章和作者数据...

  • 现在简书取消抽奖活动了吗?

    刚才我点开简书,无意中发现简书抽奖的标志没了。亲们,简书这是取消抽奖活动了吗? 取消也罢!抽奖活动如同鸡肋,弃之不...

  • 简书抽奖

    最近在简书抽奖还挺好的,虽然也有一段时间没有抽中过一万收益加成卡了,但是最近进行抽奖,如果需要看广告,只需要看3秒...

  • 借钻1万借

    今天简书抽奖抽到了一万简书钻 看视频还有额外10次机会抽奖 何乐而不为呢

  • 2019-02-18

    简书怎么抽奖都骗人

  • 干货!Python数据分析50个实战项目(持续更新……)

    分析几十万条知乎数据,我挖掘出了这些秘密 用(大)数据全方位解读电视剧《大秦帝国之崛起》 以虎嗅网4W+文章的文本...

  • mysql 语句优化

    omc_block表三百万条数据+om_flow 表二十万条数据+其他表一百条数据左右 优化后(6.6s) 优化前...

  • Excel实战:抽奖系统

    抽奖系统 本篇适合:有一定公式基础,主要是逻辑梳理。 QQ交流群:644328490。 需求:用excel制作抽奖...

网友评论

    本文标题:用二十万条数据解读简书抽奖逻辑

    本文链接:https://www.haomeiwen.com/subject/oipwwrtx.html