Pandas -----------------------案例(九)
案例一:筛选出2023年销售额总额TOP5的品牌以及对应的销售额
专注于户外运动的巨头公司。公司旗下有20个品牌,每个品牌都涉及128个类目(行业)
做一张汇总报表,包含2023年销售总额排名前五的品牌以及对应的销售额。
业务部门的同事总共发来了128张表
每张表里是一个细分行业的数据
每张表都以月的维度,从2022年1月到2023年12月,时间跨度为2年,记录着每个品牌的日期、访客数、客单价、转化率、所属类目(细分行业)等数据
输入 :
输出:
加载数据
单张表处理
import pandas as pd #加载数据 name = '睡袋&睡袋.xlsx' data_dir = '/root/pandas_code_ling/data/a_report/' df = pd.read_excel(data_dir+name,parse_dates=[0]) df.head()
批量处理
data_dir = '/root/pandas_code_ling/data/a_report/'
分析
① 先添加年份列
df['年'] = df['日期'].dt.year df② 再筛选出2023年的数据
df2 = df[df['年'] == 2023] df2③ 计算出销售额
df2['销售总额'] = df2['访客数']*df2['转化率']*df2['客单价'] # set_option(),让销售额正常显示,否则销售额会因为数值过大而显示科学记数法 pd.set_option('display.float_format', lambda x:'%.2f' % x) df2④ 增加行业列
#增加行业列 df_sum['行业'] = name.replace('.xlsx','') df_sumdf_sum.sort_values(by='销售总额',ascending=False).head()取前五条
(⑤ 批量处理 需要将每张表数据追加到df中 再进行聚合)
# 遍历目录的文件 for filename in os.listdir(data_dir): # print(filename) # 2.1 读取文件内容 df = pd.read_excel(data_dir + filename, parse_dates=[0])# 求每个品牌的总金额 final_df = final_df.groupby('品牌')['销售额'].sum().reset_index() print(final_df)
代码
# 批量处理
import os
# 1 准备工作: 指定操作的目录 和 新建一个空的 df
data_dir = '/root/pandas_code_ling/data/a_report/'
final_df = pd.DataFrame()
# 2 遍历目录的文件
for filename in os.listdir(data_dir):
# print(filename)
# 2.1 读取文件内容
df = pd.read_excel(data_dir + filename, parse_dates=[0])
# 2.2 增加年份列
df['年份'] = df['日期'].dt.year
# 2.3 过滤 2023年的数据
df_2023 = df[df['年份']==2023]
# 2.4 计算 销售额
df_2023['销售额'] = df_2023['访客数'] * df_2023['转化率'] * df_2023['客单价']
# print(df_2023.head())
# 2.5 新计算结果: 求每个详细类目每个品牌的总金额
result = df_2023.groupby("品牌")['销售额'].sum().reset_index()
# print(result)
# 2.6 将 新计算结果
final_df = pd.concat([final_df, result])
# print(final_df.head())
# 3 求每个品牌的总金额
final_df = final_df.groupby('品牌')['销售额'].sum().reset_index()
print(final_df)
# 4 排序 取前5名
final_df = final_df.sort_values("销售额", ascending=False)
print(final_df.head())
案例二:批量处理与品牌投放分析
任务:找到近一年(2023年)投放效果还不错的品牌,进行分析
加载数据
import pandas as pd
import os
dir_path = '/root/pandas_code_ling/data/b_ad_data/'
data = pd.read_excel(dir_path+'2023-12.xlsx')
# print(data.info())
# data.head()
data
分析
①添加日期
#final = pd.DataFrame() data['日期'] = name[:4] data②计算指标
data['品牌搜索点击转换率'] = data['点击人数']/data['品牌搜索人数'] data['点击支付转换率'] = data['支付人数']/data['点击人数'] data③筛选数据2023年
df = data[data['日期']=='2023'] df④维度汇总
gp = df.groupby('品牌')[['品牌搜索人数','点击人数','支付人数']].sum().reset_index() gp = gp.sort_values('品牌搜索人数',ascending = False) gp.head()批量处理
import pandas as pd import os dir_path = '/root/pandas_code_ling/data/b_ad_data/' #批量循环汇总 final = pd.DataFrame() for name in os.listdir(dir_path): df1 = pd.read_excel(dir_path + name) #取文件名的年份作为日期列 df1['日期'] = name[:4] final = pd.concat([final,df1])
代码
# 批量处理
import pandas as pd
import os
dir_path = '/root/pandas_code_ling/data/b_ad_data/'
#批量循环汇总
final = pd.DataFrame()
for name in os.listdir(dir_path):
df1 = pd.read_excel(dir_path + name)
#取文件名的年份作为日期列
df1['日期'] = name[:4]
final = pd.concat([final,df1])
#
# #筛选出年份为2023年的数据,即场景下的近一年
final_last = final[final['日期']=='2023']
final_last = final_last.groupby('品牌')[['品牌搜索人数','点击人数','支付人数']].sum().reset_index()
final_last['品牌搜索点击转换率'] = final_last['点击人数']/final_last['品牌搜索人数']
final_last['点击支付转换率'] = final_last['支付人数']/final_last['点击人数']
final_last.sort_values(by='品牌搜索点击转换率',ascending=False)
#绘图 单张表
from matplotlib import pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
#筛选出TOP15的品牌
draw_data = df.iloc[:15,:]
#设置画布大小
plt.figure(figsize=(18,11))
#筛选对应的x,y值和标签名
x = draw_data['品牌搜索点击转换率'].to_list()
y = draw_data['点击支付转换率'].to_list()
z = draw_data['品牌搜索人数']
text = draw_data['品牌'].to_list()
#绘制气泡图
plt.scatter(x, y, s = z / 20, c = x, cmap = "Reds", alpha = 0.7, edgecolors = "grey", linewidth = 1)
#为每个值打上对应品牌名
for i,txt in enumerate(text):
plt.text(x=x[i], y=y[i], s=txt, size=18, horizontalalignment='center', verticalalignment='center')
plt.xlabel("搜索-点击转化率")
plt.ylabel("点击-支付转化率")
plt.title("TOP15品牌搜索分布",size = 15)
plt.show()
#绘图 批量 多张表
from matplotlib import pyplot as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
#筛选出TOP15的品牌
draw_data = final_last.iloc[:15,:]
#设置画布大小
plt.figure(figsize=(18,11))
#筛选对应的x,y值和标签名
x = draw_data['品牌搜索点击转换率'].to_list()
y = draw_data['点击支付转换率'].to_list()
z = draw_data['品牌搜索人数']
text = draw_data['品牌'].to_list()
#绘制气泡图
plt.scatter(x, y, s = z /300,c = x, cmap = "Reds", alpha = 0.7, edgecolors = "grey", linewidth = 1)
#为每个值打上对应品牌名
for i,txt in enumerate(text):
plt.text(x=x[i], y=y[i], s=txt, size=18, horizontalalignment='center', verticalalignment='center')
plt.xlabel("搜索-点击转化率")
plt.ylabel("点击-支付转化率")
plt.title("TOP15品牌搜索分布",size = 15)
plt.show()
案例三:用户评论文本挖掘
项目需求:
竞品销售情况细化:通过对竞品评论中分型号的历史评论数量,反推竞品的主要售卖产品的情况
竞品高分和低分的具体发声:高分4-5分的评论主要是说哪些,低分1-2分用户主要说什么,我们比较关注的方面又说了什么
技术实现:
竞品细化
高分、低分关键词提取
前期准备
导包
import pandas as pd import re import math import datetime import matplotlib.pyplot as plt import seaborn as sns plt.style.use('fivethirtyeight') import warnings # 忽略不必要的warnings warnings.filterwarnings('ignore') # nltk:文本处理的包 from nltk.stem.wordnet import WordNetLemmatizer # 词形还原 from nltk.corpus import wordnet as wn from collections import Counter import pyecharts.options as opts from pyecharts.charts import WordCloud %matplotlib inline
加载数据
import pandas as pd # 1 加载数据 # 1.1 加载数据 '../data/g_reviews.csv' df_reviews = pd.read_csv('/root/pandas_code_ling/data/g_reviews.csv') # 1.2 查看内容 print(df_reviews.head()) # 1.3 查表表结构 print(df_reviews.info())
数据处理
# 2.1 去掉有缺失值的行
df_reviews= df_reviews.dropna() df_reviews.info()
# 2.2 截取评论中的星级数据 stars_num
df_reviews['new_stars'] = df_reviews['stars'] def get_stars(x): return float(x.replace(' out of 5 stars','')) df_reviews['new_stars'] = df_reviews['new_stars'].apply(get_stars) df_reviews['new_stars']
# 2.3 根据评星数量获取评级属性: 好评(4星及以上) 中评(3星) 差评(2星及以下) get_stars
df_reviews['get_stars'] = df_reviews['new_stars'] def get_kind(x): if x >= 4 : return '好评' if x >= 3 : return '中评' else : return '差评' df_reviews['get_stars'] = df_reviews['get_stars'].apply(get_kind) df_reviews['get_stars']
# 2.4 处理评论日期 date_d
处理评论日期 Reviewed in the United States on June 24, 2020
用 'on ' 去拆分, 把日期文本拆分成两部分 取后者
df_reviews['new_date'] = df_reviews['date'] def change_date(x): x = x.split('on ') # 后面发现不用根据逗号分割就可以转换为日期类型 # y = x[1].split(',') # z = y[0] + y[1] return x[1] df_reviews['new_date'] = df_reviews['new_date'].apply(change_date)
# 2.5 将 date_d 转化成 日期对象
df_reviews['new_date'] =df_reviews['new_date'].astype('datetime64[ns]') df_reviews['new_date']
# 2.6 从 date_d 中提取 年-月(y_m)
df_reviews['new_date'] = df_reviews['new_date'].astype('datetime64[M]') df_reviews['new_date']
3.1 查看不同商品的总数
df_reviews.groupby(by='product_name')['product_name'].count()
法一
df_reviews.groupby(by='product_name')['product_name'].count().plot(kind = 'bar')
法二
df_reviews['product_name'].value_counts().plot(kind='bar')
3.2 查看不同商品 不同评价 的数量
"""
构建结构化多绘图网格,不同子集上绘制同一图的多个实例, --> sns.FacetGrid()
FacetGrid 参数说明
data 绘图用到的数据
col 每一个小图 利用哪一列来截取数据
col_wrap 小图有几列, sharex是否共享x轴, sharey 是否共享Y轴, height图片高度, aspect 宽高比例
"""
plt.rcParams['font.sans-serif'] = ['SimHei']
g = sns.FacetGrid(
data=df_reviews,
col='product_name',
col_wrap=2,
sharex=False,
sharey=False,
height=5,
aspect=1.2
)
"""
g.map 绘制分面图中的小图,
利用 sns.countplot 绘图 ,
从df_reviews 利用 product_name分组 每组画 content_cat 不同类别的数量order 指定柱子的顺序
"""
g.map(
sns.countplot,
'get_stars',
order=['好评', '中评', '差评']
)
3.3 查看每种产品的发文数量 按月统计
# 3.3 查看每种产品的发文数量 按月统计
# 3.3.1 准备数据
df_content=df_reviews.groupby(['product_name','new_date'])['content'].count().reset_index()
# 3.3.2 结构化网格
g=sns.FacetGrid(
data=df_content,
col='product_name',
col_wrap=2,
sharey=True,
sharex=True,
height=4,
aspect =2
)
# 3.3.3 绘制小图
# 第一个参数传入的是 要调用哪个API 绘图, 后面几个参数传的是 调用 (plt.plot 的时候需要用到哪些参数, 具体的数据传入列名就行了
g.map(plt.plot,"new_date",'content',marker='1')#marker='1' 折线图每一个点会一条短线来表示
3.4 好中差评的随时间分布
# 3.4 好中差评的随时间分布
# 3.4.1 准备数据
df_content=df_reviews.groupby(['product_name','new_date','get_stars'])['content'].count().reset_index()
# 3.4.2 结构化网格
g=sns.FacetGrid(
data=df_content,
col='product_name',
hue='get_stars',
col_wrap=2,
sharey=False,
sharex=False,
height=4,
aspect =2
)
# 3.4.3 绘制小图
g.map(plt.plot, 'new_date', 'content', marker='.') #marker='.' 折线图每一个点会用一个点来表示
# 3.4.4 绘制图例
g.add_legend()# 添加图例
3.5 同产品不同型号的分布
# 3.5 同产品不同型号的分布
# 3.5.1 准备数据
df_content=df_reviews.groupby(['product_name','new_date','type'])['content'].count().reset_index()
# 3.5.2 结构化网格
g=sns.FacetGrid(
data=df_content,
col='product_name',
hue='type',
col_wrap=2,
sharey=False,
sharex=False,
height=4,
aspect =2)
# 3.5.3 绘制小图
g.map(plt.plot, "new_date", 'content',marker='.')
# 3.5.4 添加图例
g.add_legend()
案例四:RFM用户分群
背景知识
RFM模型是根据会员
最近一次购买时间R(Recency)
购买频率F(Frequency)
购买金额M(Monetary)计算得出RFM得分
通过这3个维度来评估客户的订单活跃价值,常用来做客户分群或价值区分
RFM模型基于一个固定时间点来做模型分析,不同时间计算的的RFM结果可能不一样
R | F | M | 用户类别 |
高 | 高 | 高 | 重要价值用户 |
高 | 低 | 高 | 重要发展用户 |
低 | 高 | 高 | 重要保持用户 |
低 | 低 | 高 | 重要挽留用户 |
高 | 高 | 低 | 一般价值用户 |
高 | 低 | 低 | 一般发展用户 |
低 | 高 | 低 | 一般保持用户 |
低 | 低 | 低 | 一般挽留用户 |
加载数据
小案例
sheet_name = ['2015','2016','2017'] s = ['ZZ21_'+ i for i in sheet_name] s加载数据
sheet_names = ["2015", "2016", "2017", "2018", "会员等级"] sheet_data = [pd.read_excel("/root/pandas_code_ling/data/h_sales.xlsx", sheet_name=sheet_name) for sheet_name in sheet_names] sheet_data
数据预处理
# 数据预处理
# 汇总所有数据, 使用pandas.concat连接前四个dataframe
df = pd.concat(sheet_data[:-1],axis=0) df.info()# 删除包含缺失值的行数据
df.dropna(inplace=True) df.info()# 保留订单金额大于1的行数据
df.query('订单金额 > 1',inplace=True) df.info()# 提取 提交日期 列的年份保存到year新列中
df['year'] = df['提交日期'].dt.year df# 对year新列分组, 提取每组中提交日期列的最大值, 保存到max_year_date新列中, 这样方便后续针对每年的数据分别做RFM计算,而不是针对4年的数据统一做RFM计算
df['max_year'] = df.groupby(by='year')['提交日期'].transform('max') df# 目标: 添加间隔日期列 # 计算日期间隔天数(该订单日期距离当年最后1天的天数),并添加列 date_interval(该列的数据类型为timedelta64[ns])df['diff_days'] = df['max_year'] - df['提交日期'] df['diff_days'] = df['diff_days'].dt.days df['diff_days']
计算 R F M 的原始值
# 计算 R F M 的原始值
# 目标: 计算 R 求分组后date_interval列中最小值, F 订单频率, M 计算订单总金额
# 1 基于year、会员ID列做分组之后,分别对date_interval、提交日期、订单金额做不同的运算
# as_index=False表示重置索引
rfm = df.groupby(by= ['year','会员ID'], as_index=False).agg({
# 1.1 R 求分组后 date_interval 列中最小值:计算当年该会员最后一次订单距离年末12月31日的间隔天数
'diff_days':'min',
# 1.2 F 订单频率,计算当年该会员一共消费多少次,也可以对 订单号 列进行count计算
'订单号':'count',
# 1.3 M 计算订单总金额:计算当年该会员一共消费多少钱
'订单金额':'sum'})
# rfm
# 2 重命名列名 'year', '会员ID', 'r', 'f', 'm'
rfm.columns = ['year', '会员ID', 'r', 'f', 'm']
rfm
查看数据分布
# 目标: 查看数据分布
# 1 获取 r, f, m 列 的所有数据
# 2 查看统计信息
# 3 转置 T
desc_pd = rfm.iloc[:,2:].describe().T
desc_pd
自定义区间边界
# 自定义区间边界,划分为3个区间,注意起始边界小于最小值
r_bins = [-1, 79, 255, 365]
f_bins = [0, 2, 5, 130] # f数据的分布比较极端,所以这里采用较小的值
m_bins = [0, 69, 1199, 206252]
计算 RFM分箱得分
案例
print('----------------- 正序')
for i in range(1, len(r_bins)):
print(i)
print('----------------- 倒序')
for j in range(len(r_bins)-1, 0, -1):
print(j)
# 目标: 计算 RFM分箱得分
# 对rfm_gb['r']的值按照r_bins进行划分,划分结果对应为新的值,新的值为labels中的对应值
# range函数:range(start, stop[, step])
# 1 计算 R 得分: r_score
rfm['r_score'] = pd.cut(rfm['r'], bins=r_bins, labels=[i for i in range(len(r_bins)-1, 0, -1)] )
# 2 计算 F 得分: f_score
rfm['f_score'] = pd.cut(rfm['f'], bins=f_bins, labels=[i for i in range(1, len(f_bins))])
# 3 计算 M 得分: m_score
rfm['m_score'] = pd.cut(rfm['m'], bins=m_bins, labels=[i for i in range(1, len(m_bins))])
rfm
计算RFM组合
import numpy as np
# 目标: 计算RFM组合
# 1 r_score、f_score、m_score 三列转为str类型
rfm['r_score'] = rfm['r_score'].astype(np.str)
rfm['f_score'] = rfm['f_score'].astype(np.str)
rfm['m_score'] = rfm['m_score'].astype(np.str)
# 2 r_score、f_score、m_score 三列拼接字符串,并添加新列 rfm_group
rfm['rfm_group'] = rfm['r_score'] + rfm['f_score'] + rfm['m_score']
rfm
绘图
# 1 根据 rfm_group 和 year 分组 再求 每组会员人数
rfm_display = rfm.groupby(by=['rfm_group','year'])['会员ID'].count().reset_index()
# 2 重命名 'rfm_group','year','number'
rfm_display.columns = ['rfm_group','year','number']
# 3 将 rfm_group 转成 np.int32 类型
rfm_display['rfm_group'] = rfm_display['rfm_group'].astype(np.int32)
rfm_display.info()
from pyecharts.charts import Bar3D
# 显示图形
from pyecharts.commons.utils import JsCode
import pyecharts.options as opts
# 颜色池
range_color = ['#313695', '#4575b4', '#74add1', '#abd9e9', '#e0f3f8', '#ffffbf',
'#fee090', '#fdae61', '#f46d43', '#d73027', '#a50026']
range_max = int(rfm_display['number'].max())
c = (
Bar3D()#设置了一个3D柱形图对象
.add(
"",#图例
[d.tolist() for d in rfm_display.values],#数据
xaxis3d_opts=opts.Axis3DOpts(type_="category", name='分组名称'),#x轴数据类型,名称,rfm_group
yaxis3d_opts=opts.Axis3DOpts(type_="category", name='年份'),#y轴数据类型,名称,year
zaxis3d_opts=opts.Axis3DOpts(type_="value", name='会员数量'),#z轴数据类型,名称,number
)
.set_global_opts( # 全局设置
visualmap_opts=opts.VisualMapOpts(max_=range_max, range_color=range_color), #设置颜色,及不同取值对应的颜色
title_opts=opts.TitleOpts(title="RFM分组结果"),#设置标题
)
)
c.render("RFM模型.html") #在notebook中显示
计算RFM分组会员数占比
# 1 按 RFM_Group 分组之后,对number求和,返回新的df
rfm_persent = rfm_display.groupby(['rfm_group'])['number'].sum().reset_index()
# 2 计算当前RFM_Group分组中会员总数百分比,并添加列 count_per
rfm_persent['count_per'] = rfm_persent['number'] / rfm_display['number'].sum()
# 3 转换为百分数,保留2位小数
rfm_persent['count_per'] = rfm_persent['count_per'].apply(lambda x: format(x, '.2%'))
# 4 按number列进行排序,由大到小
rfm_persent.sort_values('number', ascending=False, inplace=True)
rfm_persent
导出
- 导出为excel
rfm.to_excel('sales_rfm_score1.xlsx') # 保存数据为Excel
-
导出到mysql里
# 需要安装pymysql,部分版本需要额外安装sqlalchemy
# 导入sqlalchemy的数据库引擎
from sqlalchemy import create_engine
# 创建数据库引擎,传入url规则的字符串
engine = create_engine('mysql+pymysql://root:123456@192.168.88.100:3306/test?charset=utf8')
# mysql+pymysql://root:123456@192.168.88.100:3306/test?charset=utf8
# mysql 表示数据库类型
# pymysql 表示python操作数据库的包
# root:123456 表示数据库的账号和密码,用冒号连接
# 192.168.88.100:3306/test 表示数据库的ip和端口,以及名叫test的数据库
# charset=utf8 规定编码格式
# df.to_sql()方法将df数据快速写入数据库
rfm.to_sql('rfm_gb', engine, index=False, if_exists='append')
# 第一个参数为数据表的名称
# 第二个参数engine为数据库交互引擎
# index=False 表示不添加自增主键
# if_exists='append' 表示如果表存在就添加,表不存在就创建表并写入
案例五:用户偏好分析和TGI
目标:筛选出5个有高客单价偏好的城市
背景知识
TGI = 目标群体中具有某一特征的群体所占比例 / 总体中具有相同特征的群体所占比例 × 100
TGI具体数值是围绕100这个值来解读的:
TGI=100,表示目标群体和总体在某特征或行为上的表现相同。
TGI>100,表示目标群体在某特征或行为上的表现高于总体,具有较高的偏好程度,数值越大偏好越强。
TGI<100,表示目标群体在某特征或行为上的表现低于总体,具有较低的偏好程度,数值越小偏好越弱。
加载数据
import pandas as pd
# 1 加载数据
df = pd.read_excel("/root/pandas_code_ling/data/j_PreferenceAnalysis.xlsx")
print(df.info())
df
5.1 求每个用户的平均实付金额
# 2 求每个用户的平均实付金额
# 2.1 求每个用户的平均实付金额 gb_user
gb_user = df.groupby(['省份', '城市', '用户ID'])['实付金额'].mean().reset_index()
# 2.2 起别名 '省份', '城市', '用户ID', '平均每次支付金额'
gb_user.columns = ['省份', '城市', '用户ID', '平均每次支付金额']
gb_user.head()
5.2 增加一列显示客单价类别
# 3 增加一列显示客单价类别
# 3.1 方式一
def get_kdj(x):
if x > 50:
return "高客单价"
else:
return "低客单价"
gb_user['客单价类型'] = gb_user['平均每次支付金额'].apply(get_kdj)
# gb_user.head()
# 3.2 方式二
gb_user['客单价类别'] = gb_user['平均每次支付金额'].apply(lambda x: "高客单价" if x>50 else "低客单价")
gb_user.head()
5.3 求每个城市 不同客单价类别的人数
# 4 求每个城市 不同客单价类别的人数
# 4.1 先筛选出需要的列 "省份", "城市", "用户ID", "客单价类别"
gb_user = gb_user[["省份", "城市", "用户ID", "客单价类别"]]
# 4.2 使用透视表 求每个城市 不同客单价类别的人数
tgi = pd.pivot_table(data=gb_user, index=["省份", "城市"], columns="客单价类别", aggfunc="count").reset_index()
tgi.head()
5.4 计算 总人数,高客单价占比
# 5 计算 总人数,高客单价占比
# 5.1 求 总人数
# gb_user['用户ID']['高客单价']
tgi['总人数'] = tgi['用户ID']['高客单价'] + tgi['用户ID']['低客单价']
# 5.2 求 高客单价占比
tgi['高客单价占比'] = tgi['用户ID']['高客单价'] / tgi['总人数']
tgi
5.5 删除空值
tgi.dropna()
5.6 统计总人数中高客单人群的比例 = 高客单价总人数 / 总人数
# 7 统计总人数中高客单人群的比例 = 高客单价总人数 / 总人数
total_percent = tgi['用户ID']['高客单价'].sum() / tgi['总人数'].sum()
total_percent
5.7 计算 TGI 的值
# 8 计算 TGI 的值
# 8.1 求 高客单价TGI = 局部占比 / 整体占比 * 100
tgi['高客单价TGI'] = tgi['高客单价占比'] / total_percent * 100
# 8.2 排序 按 高客单价TGI
tgi.sort_values("高客单价TGI", ascending=False).head()
5.8 TGI计算优化
# 用总人数的平均值作为阈值,只保留总人数大于平均值的城市
tgi[tgi['总人数']>tgi['总人数'].mean()].sort_values("高客单价TGI", ascending=False).head()
案例六:同期群分析 计算留存率 留存金额 留存客单
背景知识
概念:
同期群分析其核心逻辑是将用户按初始行为的发生时间划分为不同的群组,进而分析相似群组的行为如何随时间变化
举例:
下表记录了每个月新购买的用户数,并统计每个月的新增用户在之后月份的复购情况
数据截止到2023年7月,如图下所示。
数据的第一行,2023年1月有97个新用户
之后的+1月(2023年2月)有46%的用户再次光顾
+2月(2023年3月)仍有39%的回头客
第一行的46%、39%都是对应复购人数占2023年1月新增购买用户的比重,这些人属于2023年1月同一期的新增用户
其他行也是一样的道理,每一行为同一个群组,反映同一期新增用户在之后一段时间复购行为的变化趋势
同期群分析的价值
基于同期群分析:
横向,我们可以知道每个月新增用户的留存情况,发现2023年1~4月购买的用户,+1月回购的概率在50%左右,随后依次递减,但+5月后的留存率断档明显。
纵向,对比不同月份新增和留存情况,很容易发现2023年1~2月受春节假期影响,新增用户较少,3~5月处于增长恢复期,而6月新增用户数成倍增长,有些异常
加载数据
import pandas as pd
# 1 加载数据
df = pd.read_excel("/root/pandas_code_ling/data/k_group_anlysis.xlsx")
print(df.info())
print('-------------------------')
print(df.head())
6.1 统计每个月的订单量
#法一
# 1.1 加入 y_m 月份标签
df['y_m'] = df['付款时间'].astype(str).str[:7]
df['y_m'].unique()
# 1.2 统计每个月的订单量 且 排序
df['y_m'].value_counts().sort_index()
#法二
df['y-m'] = df['付款时间'].astype('datetime64[M]')
df.groupby('y-m')['主订单编号'].count().reset_index().sort_values('y-m')
6.2 求 2月份 的订单量 和 用户数量
# 求 2月份 的订单量 和 用户数量
#法一:
# 指定月份 '2023-02'
month = '2023-02'
# 筛选出指定月份相关的订单(订单数量)
sample = df[df['y_m'] == month]
print("二月份的订单量: ", len(sample))
# 求 指定月份 每个用户的总实付总额(用户数量)
sample_c = sample.groupby('用户ID')['实付金额'].sum().reset_index()
print("二月份的用户量: ", len(sample_c))
# 打印 3.3的前5条数据
sample_c.head()
#法二
df1 = df[df['y-m'] =='2023-02-01']
df2=df1['主订单编号'].count()
print(df2)
df3 = df1['用户ID'].unique()
print(len(df3))
df4 = df1.groupby('用户ID')['实付金额'].sum().reset_index()
print(df4.head())
6.3 求 2月之前的历史订单
history = df[df['y_m'] == '2023-01']
# 用法二 应该是'2023-01-01'
history.head()
6.4 筛选出2023年2月新增的用户数
# 筛选出2023年2月新增的用户数
sample_new = sample_c.loc[sample_c['用户ID'].isin(history['用户ID'])==False, :]
print('2月份新增用户数: ', len(sample_new))
sample_new.head()
6.5 计算2023年2月在后面的留存情况 得到完整2023年2月份同期群结果
# 计算2023年2月在后面的留存情况
# 1 准备容器
re = []
# 2 遍历 ['2023-03', '2023-04', '2023-05', '2023-06','2023-07','2023-08','2023-09','2023-10','2023-11','2023-12']
for month in ['2023-03', '2023-04', '2023-05', '2023-06','2023-07','2023-08','2023-09','2023-10','2023-11','2023-12']:
# print(month)
# 2.1 获取下个月的数据
next_month = df.loc[df['y_m'] == month, :]
# print('---------------------')
# print(next_month)
# 2.2 获取留存的数据
target_user = sample_new.loc[sample_new['用户ID'].isin(next_month['用户ID']), :]
# 2.3 向容器中追加 yyyy-mm留存情况: 999
re.append(f"{month} 留存情况: {len(target_user)}")
# 3 验证
re
6.6 遍历合并和分析
# 1 月份列表 month_lst
month_lst = df['y_m'].unique()
# 2 准备最终容器 final
final = pd.DataFrame()
# 3 遍历月份列表
for i in range(len(month_lst)):
# print(i, month_lst[i])
# 3.1 构造和月份一样长的列表,方便后续格式统一 count
count = [0] * len(month_lst)
# print(count)
# 3.2 筛选出当月订单,并按用户ID分组 target_month
target_month = df[df['y_m'] == month_lst[i]]
# print('---------------------------')
# print(target_month)
# 3.3 求当月订单每个用户的实付金额 target_users
target_users = target_month.groupby("用户ID")['实付金额'].sum().reset_index()
# print('---------------------------')
# print(month_lst[i], len(target_users))
# 3.4 判断是否是第一个月
if i == 0:
# 3.4.1 如果是第一个月,则跳过(因为不需要和历史数据验证是否为新增用户) new_target_users
new_target_users = target_month.groupby("用户ID")['实付金额'].sum().reset_index()
else:
# 3.4.2 如果不是第一个月
# 3.4.2.1 找到之前的历史订单 history
history = df.loc[df['y_m'].isin(month_lst[:i])]
# print(history)
# print('-------------------------')
# 3.4.2.2 筛选出未在历史订单中出现过的新增用户 new_target_users
new_target_users = target_users.loc[target_users['用户ID'].isin(history["用户ID"])==False,:]
# 3.5 将当月新增用户数放在第一个值中
count[0] = len(new_target_users)
# print(count)
# 3.6 以月为单位,循环遍历,计算留存情况
"""
for j, ct in zip(range(i+1,len(month_lst)), range(1, len(month_lst))):
i = 2
i+1=3, 12, [3, 4, 5, 6, 7, 8, 9, 10, 11]
1, 12, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
i = 3
i+=4, 12, [4, 5, 6, 7, 8, 9, 10, 11]
1, 12, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
"""
for j, ct in zip(range(i+1,len(month_lst)), range(1, len(month_lst))):
# 3.6.1 下一个月的订单 next_month
next_month = df.loc[df['y_m'] == month_lst[j], :]
next_users = next_month.groupby("用户ID")['实付金额'].sum().reset_index()
# 3.6.2 计算在该月仍然留存的用户数量
isin = new_target_users['用户ID'].isin(next_users['用户ID']).sum()
count[ct] = isin
# print(count)
# 3.7 格式转置
result = pd.DataFrame({month_lst[i] : count}).T
# print(result)
# print('-------------------------------')
# 3.8 合并
final = pd.concat([final, result], axis=0)
# 4 指定列名 '当月新增','+1月','+2月','+3月','+4月','+5月','+6月','+7月','+8月','+9月','+10月','+11月'
final.columns = ['当月新增','+1月','+2月','+3月','+4月','+5月','+6月','+7月','+8月','+9月','+10月','+11月']
# 5 验证
final
6.7 调成留存率
# 1 让final每个元素 除以 当月新增, 只保留 第二列往后的列内容
result = final.divide(final['当月新增'], axis = 0).iloc[:,1:]
# 2 新增当月新增列
result['当月新增'] = final['当月新增']
# 3 验证
result
6.8 回购客单的同期群实现--留存金额
#引入y_m
month_lst = df['y_m'].unique()
#后面加了个m,代表金额相关
final_m = pd.DataFrame()
#中间代码相同
for i in range(len(month_lst) - 1):
#构造和月份一样长的列表,方便后续格式统一
count = [0] * len(month_lst)
#筛选出当月订单,并按用户昵称分组
target_month = df.loc[df['y_m'] == month_lst[i],:]
target_users = target_month.groupby('用户ID')['实付金额'].sum().reset_index()
#如果是第一个月,则跳过(因为不需要和历史数据验证是否为新增用户)
if i == 0:
new_target_users = target_month.groupby('用户ID')['实付金额'].sum().reset_index()
else:
#如果不是,找到之前的历史订单
history = df.loc[df['y_m'].isin(month_lst[:i]),:]
#筛选出未在历史订单中出现过的新增用户
new_target_users = target_users.loc[target_users['用户ID'].isin(history['用户ID']) == False,:]
#将当月新增用户数放在第一个值中
count[0] = len(new_target_users)
#以月为单位,循环遍历,计算留存情况
for j,ct in zip(range(i + 1,len(month_lst)),range(1,len(month_lst))):
#下一个月的订单
next_month = df.loc[df['y_m'] == month_lst[j],:]
next_users = next_month.groupby('用户ID')['实付金额'].sum().reset_index()
#计算在该月仍然留存的用户的回购金额
isin_m = next_users.loc[next_users['用户ID'].isin(new_target_users['用户ID']) == True,'实付金额'].sum()
count[ct] = isin_m
#格式转置
result = pd.DataFrame({month_lst[i]:count}).T
#合并
final_m = pd.concat([final_m,result])
final_m.columns = ['当月新增','+1月','+2月','+3月','+4月','+5月','+6月','+7月','+8月','+9月','+10月','+11月']
final_m
6.9 回购客单的同期群实现--留存客单
result_m = final_m / final
result_m['当月新增'] = final_m['当月新增']
result_m
原文地址:https://blog.csdn.net/weixin_54353942/article/details/142745530
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!