背景
Python初步分析订单数据,如果是学习需要虚拟数据,则可以参考我的这篇文章自定义一些虚拟数据。
定义
复购率:复购率是指在一个特定的时间窗口内,多次购买同一产品或服务的客户占该时间段内总客户数的比例。它主要关注的是客户在同一时间段内的重复购买行为,衡量的是客户的消费频率和忠诚度。比如,在一个月内,购买过两次或以上的客户占该月总客户数的比例就是该月的复购率。
回购率:回购率关注的是客户在不同时间段内的再次购买行为。它计算的是在一个时间段内购买过产品或服务的客户,在后续的时间段内再次购买的比例。例如,如果一个客户在1月份购买了产品,然后在2月份又进行了购买,那么他就属于回购客户,回购率则是这类客户占所有1月份客户的比例。
这中间就涉及到产品生命周期的一些问题了,具体的这个时间界定还是得看商家产品生命周期有多长。
—————————————————————————————————————————————————————
留存率:留存率是一个用于反映互联网应用、网站或网络游戏运营情况的统计指标。它具体表示在某一统计周期(如周或月)内,那些在某日活跃的用户在接下来的一段时间(如第2日、第7日、第30日等)内仍然保持活跃的比例。留存率常用于衡量用户粘性,即用户对于应用的持续使用意愿和忠诚度。
留存率的计算公式为:留存率 = (留存用户数 ÷ 初始用户数) × 100%。其中,留存用户数指的是在特定时间周期内仍然活跃的用户数量,而初始用户数则是该周期开始时的活跃用户数量。
留存率根据统计的时间周期不同,可以分为次日留存率、三日留存率、周留存率、半月留存率和月留存率等。通常,N取值越大,而留存率越高时,说明用户的粘性越高,应用或网站的运营效果越好。
————————————————————————————————————————————————-—————
RFM是客户关系管理中的一种分析模式,也是衡量客户价值和客户创利能力的重要工具和手段。它具体包含三个关键要素:
R(Recency):指客户最近一次消费的时间间隔。这个指标主要反映了客户的活跃程度,R值越小,表示客户的活跃程度越高;R值越大,则表示客户上一次交易的时间越久远,可能面临流失的风险。
F(Frequency):指客户在最近一段时间内消费的次数。F值越大,说明客户消费频次越高,越活跃。
M(Monetary):指客户在最近一段时间内消费的金额。M值越高,则客户消费金额越大,可能属于高价值或VIP客户。
————————————————————————————————————————————————-—————
分析
import pandas as pd
import pymysql
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
# 数据库连接配置
config = {
'host': 'localhost', # 数据库地址
'port': 3306, # 端口号
'user': 'root', # 数据库用户名
'password': '***', # 数据库密码
'db': 'data', # 数据库名称
'charset': 'utf8mb4', # 字符集
}
# 连接数据库
db = pymysql.connect(**config)
cursor = db.cursor()
- 每个订单的初步分析
# 每个订单的初步分析
df = pd.read_sql(
"select customer_name,left(order_create_time,10) as order_create_time,item_num,cast(total_money as float ) AS total_money from order_data WHERE order_status = '交易成功'",
db)
print(df.describe())
"""
item_num total_money
count 507.000000 507.000000
mean 5.459566 2594.013807
std 2.863123 1350.196286
min 1.000000 303.000000
25% 3.000000 1439.000000
50% 5.000000 2614.000000
75% 8.000000 3786.500000
max 10.000000 4973.000000
平均购买5.5个商品,标准差在2.8个左右,中位数是购买5个商品,75分位数在购买8个商品,这么这个店铺绝大多数的订单量都不错。
基本购买数量和金额都成线性增长。
"""
- 每个用户的初步分析
# 每个用户的初步分析
df_group = df.groupby('customer_name').sum()
print(df_group.describe())
"""
item_num total_money
count 243.000000 243.000000
mean 11.390947 5412.201646
std 8.378904 4069.483713
min 1.000000 319.000000
25% 6.000000 2557.500000
50% 9.000000 4471.000000
75% 15.000000 7222.500000
max 52.000000 28617.000000
平均每个用户购买11.4件商品,消费5400元左右,标准差在8.4件左右,标准差消费4000元左右,说明用户消费差异大,中位数在9件的样子,75分位数在15件的样子,最多一个用户购买了52件,花费了2.8W元。
"""
- 按照月度初步分析
# 按照月度初步分析
df['month'] = df['order_create_time'].apply(lambda x: x[0:7])
df.groupby('month').item_num.sum().plot()
df.groupby('month').total_money.sum().plot()
plt.show()
表明销量成周期性变化,平均半年一个周期,
下面是销售额的数据
可以看出基本与销售量成线性匹配,说明该订单类型应该属于标品类型。
- 绘制每笔订单的散点图
#绘制每笔订单的散点图
df.plot.scatter(x='total_money', y='item_num')
结果如下
这边因为订单生成原因,所以没什么,如果真实情况和图中类似,建议可以拓宽价格带,这个说明你的价格带区间很均匀,你的商品并没有涉及到商品价格边缘。
- 按照用户分组的散点图
df.groupby('customer_name').sum().plot.scatter(x='total_money', y='item_num')
结果
用户主要消费水平在1W以下,数量和金额成正比。
- 观察不同用户的消费能力
# 观察不同用户的消费能力
plt.figure(figsize=(12,4))
plt.subplot(121)
df.total_money.hist(bins=30)
plt.subplot(122)
df.groupby('customer_name').item_num.sum().hist(bins=30)
结果:
从直方图看,大部分用户的消费能力可以,高消费用户几乎和低消费用户持平。消费数量在10件之后成指数性降低,这也符合消费规律。
- 消费时间分析
# 将用户分组,求月份的最小值,即用户消费行为中的第一次消费时间
df_month_min = pd.DataFrame(df.groupby('customer_name').month.min().value_counts())
df_month_min.reset_index(level=0, inplace=True)
# 按照时间排序
df_month_min = df_month_min.sort_values(by='month')
df_month_min.plot.scatter(x='month', y='count')
xticks, xticklabels = plt.xticks()
plt.xticks(xticks, xticklabels, rotation=90)
plt.show()
结果
df_month_max = pd.DataFrame(df.groupby('customer_name').month.max().value_counts())
df_month_max.reset_index(level=0, inplace=True)
# 按照时间排序
df_month_max = df_month_max.sort_values(by='month')
df_month_max.plot.scatter(x='month', y='count')
xticks, xticklabels = plt.xticks()
plt.xticks(xticks, xticklabels, rotation=90)
plt.show()
结果
由于我的数据是自制数据,并没有太多参考意义,如果有需要可以参考我当初学习的原文。
第一种写法
import pymysql
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
# pandas展示所有列
pd.set_option('display.max_columns', None)
# 数据库连接配置
config = {
'host': 'localhost', # 数据库地址
'port': 3306, # 端口号
'user': 'root', # 数据库用户名
'password': '***', # 数据库密码
'db': 'data', # 数据库名称
'charset': 'utf8mb4', # 字符集
}
# 连接数据库
db = pymysql.connect(**config)
cursor = db.cursor()
根据定义,统计月复购次数大于1的用户
# 复购率:自然月内,购买多次的用户占比(即,购买了两次以上)
df = pd.read_sql("select customer_name,left(order_create_time,10) as order_create_time,left(order_create_time,7) as month,cast(total_money as float ) AS total_money from order_data WHERE order_status = '交易成功'", db)
# 对于用户名和月份作为聚合条件对于日期进行统计次数
df = pd.DataFrame(df.groupby(['customer_name', 'month'])['order_create_time'].count())
df.reset_index(level=1, inplace=True)
df.reset_index(level=0, inplace=True)
df.rename(columns={'order_create_time': 'count'}, inplace=True)
print(df[df['count']>1])
代码结果为
统计每个月的月复购大于1的人数
# 每个月复购人数
df_count = pd.DataFrame(df_count.groupby('month')['count'].count())
print(df_count)
结果为
然后求取每个月的购买总人数
# 统计每个月的人数
df_person = pd.DataFrame(df.groupby('month').agg({'customer_name': 'nunique'}))
df_person.rename(columns={'customer_name': 'count'}, inplace=True)
df_person.reset_index(level=0, inplace=True)
print(df_person)
结果为:
用 每个月的复购人数除以每个月的总人数得出每个月的复购率
# 用 每个月的复购人数除以每个月的总人数得出每个月的复购率
merged_outer = pd.merge(df_count, df_person, on='month', how='outer')
merged_outer['rate'] = merged_outer.apply(lambda x: 0 if pd.isnull(x.count_x) else x.count_x / x.count_y, axis=1)
print(merged_outer)
结果为
另一种写法
# 表透视
pivoted_counts = df.pivot_table(index='customer_name',
columns='month',
values='order_create_time',
aggfunc='count').fillna(0)
print(pivoted_counts)
``
import numpy as np
# 将数据转换一下,消费两次及以上记为1,消费一次记为0,没有消费记为NaN
purchase_r = pivoted_counts.applymap(lambda x: 1 if x > 1 else np.NAN if x == 0 else 0)
print(purchase_r)
结果为
画图
复购高点基本三个季度一个周期。
获取数据库配置
import matplotlib.pyplot as plt
import pymysql
import pandas as pd
import numpy as np
# 数据库连接配置
config = {
'host': 'localhost', # 数据库地址
'port': 3306, # 端口号
'user': 'root', # 数据库用户名
'password': '***', # 数据库密码
'db': 'data', # 数据库名称
'charset': 'utf8mb4', # 字符集
}
# 连接数据库
db = pymysql.connect(**config)
cursor = db.cursor()
获取数据并进行表透视
df = pd.read_sql(
"select customer_name,left(order_create_time,10) as order_create_time,left(order_create_time,7) as month,cast(total_money as float ) AS total_money from order_data WHERE order_status = '交易成功'",
db)
# 表透视
pivoted_amount = df.pivot_table(index='customer_name',
columns='month',
values='total_money',
aggfunc='mean').fillna(0)
pivoted_amount = pivoted_amount.applymap(lambda x: 1 if x > 0 else 0)
新建一个判断函数,status用来保存用户是否回购的字段。
如果用户本月进行过消费,且下月消费过,记为1,没有消费过是0。如果本月没有进行过消费,为NaN。
def purchase_return(data):
status = []
for i in range(17):
if data[i] == 1:
if data[i + 1] == 1:
status.append(1)
if data[i + 1] == 0:
status.append(0)
else:
status.append(np.NaN)
status.append(np.NaN)
dic = dict(map(lambda x, y: [x, y], data.index, status))
series = pd.Series(dic)
return series
pivoted_amount_return = pivoted_amount.apply(purchase_return, axis=1)
print(pivoted_amount_return)
计算回购率
#用户本月进行过消费,且下月消费过/(用户本月进行过消费,且下月消费过 + 用户本月进行过消费,且下月没有消费过)
huogou = pivoted_amount_return.sum() / pivoted_amount_return.count()
huogou_month = pd.DataFrame(huogou)
huogou_month.reset_index(level=0, inplace=True)
print(huogou_month)
# 画图
(pivoted_amount_return.sum()/pivoted_amount_return.count()).plot(figsize=(8, 5))
plt.show()
结果
说明基本一个季度一个周期,会有一个回购高峰期,回购率远高于复购率。
而当回购率远高于复购率时,这通常意味着:
客户忠诚度差异:回购率是指曾经购买过的客户再次购买的比例,而复购率则是指同一客户在一定时间内多次购买的比例。如果回购率高而复购率低,可能表明虽然有很多老客户选择再次购买,但这些客户中大部分并没有频繁地、多次地购买。
一次性购买行为较多:高回购率低复购率可能表示很多客户只是偶尔购买,而不是频繁地或定期地购买。这可能是因为他们的需求不是持续性的,或者他们只是出于某种特定原因(如促销、新品尝鲜等)进行了单次购买。
缺乏持续吸引客户的策略:这种情况可能也反映出企业在维持客户长期购买兴趣方面做得不够。例如,可能缺乏有效的客户忠诚度计划、产品更新迭代不够快、服务体验不够优质等,导致客户虽然愿意回来购买,但不愿意频繁购买。
新客户与老客户的比例:如果回购率高,可能意味着企业吸引新客户的能力较强,但这些新客户可能没有转化为频繁购买的忠实客户。
为了改善这种情况,企业可能需要:
深入了解客户的需求和购买习惯,以提供更加符合他们期望的产品和服务。
加强客户关系管理,通过定期沟通、优惠活动等方式提高客户粘性。
不断优化产品和服务,提升客户体验,从而增加客户的复购意愿。
按照用户的消费行为,分成几个维度:新用户、活跃用户、不活跃用户、回流用户。
将还没有消费的命名为未注册用户,
用户有过第一次消费命名为新用户,
新用户之后如果这个月消费,上个月也是消费的,命名为活跃用户,如果上个月不消费的命名为回流用户
新用户之后这个月和上个月都不消费的命名为不活跃用户
以上的时间窗口都是按月统计。
数据库配置获取数据
import pymysql
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# pandas展示所有列
pd.set_option('display.max_rows', 100)
# 数据库连接配置
config = {
'host': 'localhost', # 数据库地址
'port': 3306, # 端口号
'user': 'root', # 数据库用户名
'password': '***', # 数据库密码
'db': 'data', # 数据库名称
'charset': 'utf8mb4', # 字符集
}
# 连接数据库
db = pymysql.connect(**config)
cursor = db.cursor()
df = pd.read_sql(
"select customer_name,left(order_create_time,10) as order_create_time,left(order_create_time,7) as month,cast(total_money as float ) AS total_money from order_data WHERE order_status = '交易成功'",
db)
表透视
# 表透视
pivoted_counts = df.pivot_table(index='customer_name',
columns='month',
values='order_create_time',
aggfunc='count').fillna(0)
df_purchase = pivoted_counts.applymap(lambda x: 1 if x > 0 else 0)
print(df_purchase)
结果
从上图中可以看到,表中数据有0,1,2···,要将有消费的变为1,没有消费变为0
这里由于进行数据透视,填充了一些 null 值为0,而实际可能用户在当月根本就没有注册,这样会误导第一次消费数据的统计。
用户量月统计
# 每个月的统计量
purchase_status_counts = purchase_data.replace(2, np.NaN).apply(lambda x: pd.value_counts(x))
print(purchase_status_counts)
purchase_status_counts.fillna(0).T.plot.area(figsize=(8, 5))
plt.show()
# 生成面积图。
# 因为只是某时间段消费过的用户的后续行为,蓝色和橙色区域不看,
# 只绿色回流和红色活跃这两个分层,用户数比较稳定。
结果
因为只是某时间段消费过的用户的后续行为,蓝色和橙色区域不看,
只绿色回流和红色活跃这两个分层,用户数比较稳定。
回流用户与活跃用户
purchase_status_counts_new = purchase_data.replace(2, np.NaN).replace(3, np.NaN).replace(4, np.NaN).apply(lambda x: pd.value_counts(x))
purchase_status_counts_new.fillna(0).T.plot.area(figsize=(8, 5))
plt.show()
# 用户数有逐年螺旋稳定上升的趋势
return_rate = purchase_status_counts.apply(lambda x: x/x.sum(), axis=0)
return_rate.loc[5].plot(figsize=(8, 5))
plt.show()
# 回流占比基本在0.1以下,说明召回客户做的不太好,并且有逐年下降的趋势。
结果
回流占比基本在0.1以下,说明召回客户做的不太好,并且有逐年下降的趋势。
用户分层标签并存储(作为标签体系数据)
def active_data(data):
# 未注册用户 = 2
# 不活跃用户 = 3
# 新用户 = 4
# 回流用户 = 5
# 活跃用户 = 6
for i in range(len(data.columns.values)):
if data[data.columns.values[i]].values[0] == 0:
if i > 0:
if data[data.columns.values[i - 1]].values[0] == 2:
data[data.columns.values[i]].values[0] = 2
else:
data[data.columns.values[i]].values[0] = 3
else:
data[data.columns.values[i]].values[0] = 2
else:
if i == 0:
data[data.columns.values[i]].values[0] = 4
else:
if data[data.columns.values[i - 1]].values[0] == 3:
data[data.columns.values[i]].values[0] = 5
elif data[data.columns.values[i - 1]].values[0] == 2:
data[data.columns.values[i]].values[0] = 4
else:
data[data.columns.values[i]].values[0] = 6
return data
# 第四步
purchase_data = df_purchase.groupby('customer_name').apply(lambda x: active_data(x))
purchase_data.replace(2, '未注册用户', inplace=True)
purchase_data.replace(3, '不活跃用户', inplace=True)
purchase_data.replace(4, '新用户', inplace=True)
purchase_data.replace(5, '回流用户', inplace=True)
purchase_data.replace(6, '活跃用户', inplace=True)
user_layering_history = pd.DataFrame(purchase_data.stack())
user_layering_history.reset_index(level=0, inplace=True)
user_layering_history.rename(columns={0: 'level'}, inplace=True)
user_layering_history_tosql = user_layering_history[user_layering_history['level'] != '未注册用户']
print(user_layering_history_tosql)
# 第五步
user_layering_history_tosql = user_layering_history_tosql.head(1000)
user_layering_history_tosql.to_sql('user_layering_history',engine,if_exists='replace')
for i in range(1000,len(user_layering_history_tosql),1000):
user_layering_history_tosql.iloc[i:i+1000].to_sql('user_layering_history',engine,if_exists='append')
结果
数据库配置以及获取数据
import pymysql
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# pandas展示所有列
pd.set_option('display.max_rows', 100)
# 数据库连接配置
config = {
'host': 'localhost', # 数据库地址
'port': 3306, # 端口号
'user': 'root', # 数据库用户名
'password': '***', # 数据库密码
'db': 'data', # 数据库名称
'charset': 'utf8mb4', # 字符集
}
# 连接数据库
db = pymysql.connect(**config)
cursor = db.cursor()
df = pd.read_sql(
"select customer_name,left(order_create_time,10) as order_create_time,left(order_create_time,7) as month,cast(total_money as float ) AS total_money from order_data WHERE order_status = '交易成功'",
db)
我们通过消费金额来查看高质量用户的分层情况
# 我们通过消费金额来查看高质量用户的分层情况
user_amount = df.groupby('customer_name').total_money.sum().sort_values().reset_index()
user_amount['total_money'] = user_amount.total_money.cumsum()
amount_total = user_amount.total_money.max()
user_amount['prop'] = user_amount.apply(lambda x: x.total_money / amount_total, axis=1)
user_amount.prop.plot()
plt.show()
print(user_amount)
结果
上图为总消费额趋势图,横坐标是按用户贡献金额大小排序而成,纵坐标则是用户累计贡献。可以清楚的看到,前150个用户贡献了40%的消费额,后面100位用户贡献了60%的消费额,呈现二八倾向,但差距不算很大。
定义第一次消费至最后一次消费为整个用户生命周期。
数据库配置以及获取数据
import pymysql
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# pandas展示所有列
pd.set_option('display.max_rows', 100)
# 数据库连接配置
config = {
'host': 'localhost', # 数据库地址
'port': 3306, # 端口号
'user': 'root', # 数据库用户名
'password': '***', # 数据库密码
'db': 'data', # 数据库名称
'charset': 'utf8mb4', # 字符集
}
# 连接数据库
db = pymysql.connect(**config)
cursor = db.cursor()
df = pd.read_sql(
"select customer_name,left(order_create_time,10) as order_create_time,left(order_create_time,7) as month,cast(total_money as float ) AS total_money,item_num from order_data WHERE order_status = '交易成功'",
db)
计算用户生命周期
df['order_create_time'] = pd.to_datetime(df['order_create_time'])
order_date_min = df.groupby('customer_name').order_create_time.min()
order_date_max = df.groupby('customer_name').order_create_time.max()
print(order_date_max - order_date_min)
print((order_date_max - order_date_min).mean())
# 平均用户生命周期半年。
结果
平均用户生命周期半年
((order_date_max - order_date_min) / np.timedelta64(1, 'D')).hist(bins=15)
plt.show()
# 大部分用户只消费了一次,生命周期集中在了0天。将只消费了一次的新客排除,来计算所有消费过两次以上的老客的生命周期。
结果
大部分用户只消费了一次,生命周期集中在了0天。将只消费了一次的新客排除,来计算所有消费过两次以上的老客的生命周期。
life_time = (order_date_max - order_date_min).reset_index()
life_time['life_time'] = life_time.order_create_time / np.timedelta64(1, 'D')
life_time[life_time.life_time > 0].life_time.hist(bins=100, figsize=(8, 5))
plt.show()
# 排除仅消费了一次的用户,做直方图。在用户首次消费90天内应该尽量引导。这个类型普通和高质量的回购都差不多。
排除仅消费了一次的用户,做直方图。在用户首次消费90天内应该尽量引导。这个类型普通和高质量的回购都差不多。
print(life_time[life_time.life_time > 0].life_time.mean())
# 消费两次以上的用户生命周期是308天,远高于总体,用户首次消费后应该引导其进行多次消费,提高生命周期。
数据库配置以及获取数据
import pymysql
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
# pandas展示所有列
pd.set_option('display.max_rows', 100)
# 数据库连接配置
config = {
'host': 'localhost', # 数据库地址
'port': 3306, # 端口号
'user': 'root', # 数据库用户名
'password': '***', # 数据库密码
'db': 'data', # 数据库名称
'charset': 'utf8mb4', # 字符集
}
# 连接数据库
db = pymysql.connect(**config)
cursor = db.cursor()
df = pd.read_sql(
"select customer_name,left(order_create_time,10) as order_create_time,left(order_create_time,7) as month,cast(total_money as float ) AS total_money,item_num from order_data WHERE order_status = '交易成功'",
db)
计算日期差
df['order_create_time'] = pd.to_datetime(df['order_create_time'])
order_date_min = df.groupby('customer_name').order_create_time.min()
user_purchase_retention = pd.merge(left=df, right=order_date_min.reset_index(),
how='inner', on='customer_name', suffixes=('', '_min'))
# 计算日期差, 将order_create_time和order_create_time_min相减,获得一个新的列,为用户每一次消费距第一次消费的时间差值。
user_purchase_retention[
'order_date_diff'] = user_purchase_retention.order_create_time - user_purchase_retention.order_create_time_min
print(user_purchase_retention)
将order_create_time和order_create_time_min相减,获得一个新的列,为用户每一次消费距第一次消费的时间差值。
结果
user_purchase_retention['date_diff'] = user_purchase_retention.order_date_diff.apply(
lambda x: x / np.timedelta64(1, 'D'))
bin = [0, 3, 7, 15, 30, 60, 90, 180, 365]
user_purchase_retention['date_diff_bins'] = pd.cut(user_purchase_retention.date_diff, bins=bin)
print(user_purchase_retention)
将时间差值分桶,分成0~3天,3~7天,7~15天等,代表用户当前消费时间距第一次消费属于哪个时间段。
这里date_diff=0并没有被划分入0~3天,因为计算的是留存率,如果用户仅消费了一次,留存率应该是0。
另外一方面,如果用户第一天内消费了多次,但是往后没有消费,也算作留存率0。
用pivot_table数据透视,获得的结果是用户在第一次消费之后,在后续各时间段内的消费总额。
pivoted_retention = user_purchase_retention.pivot_table(index='customer_name', columns='date_diff_bins',
values='total_money', aggfunc=sum)
print(pivoted_retention.mean())
结果
计算一下用户在后续各时间段的平均消费额,只统计有消费的平均值。 虽然后面时间段的金额高,但是它的时间范围也较广。
从平均效果看,用户第一次消费后的0~3天内,更可能消费多,特别是3-7天的,一点都没有。
查看整体中有多少用户在0~3天消费。
1代表在该时间段内有消费,0代表没有。
pivoted_retention_trans = pivoted_retention.fillna(0).applymap(lambda x: 1 if x > 0 else 0)
print(pivoted_retention_trans)
(pivoted_retention_trans.sum()/pivoted_retention_trans.count()).plot.bar()
plt.show()
结果
只有1%的用户在第一次消费的次日至3天内有过消费,0%的用户在3~7天内有过消费,有17%的用户在第一次消费后的三个月到半年之间有过消费,29%的用户在半年后至1年内有过消费。可以延长时间跨度,在较长时间跨度上召回用户购买。
在上面的用户生命周期基础上,将数据按照用户名和订单时间排序
user_purchase_retention = user_purchase_retention.sort_values(['customer_name', 'order_create_time'])
结果
def diff(group):
d = group.date_diff - group.date_diff.shift(-1)
return d
last_diff = user_purchase_retention.groupby('customer_name').apply(diff)
print(last_diff.mean()
结果
用户的平均消费间隔时间是166天。想要召回用户,在166天左右的消费间隔是比较好的。
last_diff.hist(bins=20)
plt.show()
结果
上图告诉我们,我们召回客户最好在50天以内进行召回,可以考虑消费后立即赠送优惠券,消费后10天询问用户消费体验,消费后30天提醒优惠券到期,消费后50天短信推送。
城市等级分析
一几年网上找的城市等级,如果需要,建议去国家网站去找一下最新数据。
import numpy as np
import pandas as pd
import pymysql
# pandas展示所有列
pd.set_option('display.max_columns', None)
pd.set_option('display.max_rows', 20)
# 网上获取的关于城市等级的数据
city_level1 = "北京、上海、广州、深圳"
city_level1_new = "成都、杭州、重庆、武汉、西安、苏州、天津、南京、长沙、郑州、东莞、青岛、沈阳、宁波、昆明"
city_level2 = "无锡、佛山、合肥、大连、福州、厦门、哈尔滨、济南、温州、南宁、长春、泉州、石家庄、贵阳、常州、南通、嘉兴、太原、徐州、南昌、金华、惠州、珠海、中山、台州、烟台、兰州、绍兴、海口、扬州"
city_level3 = "汕头、湖州、盐城、潍坊、保定、镇江、洛阳、泰州、乌鲁木齐、临沂、唐山、漳州、赣州、廊坊、呼和浩特、芜湖、桂林、银川、揭阳、三亚、遵义、江门、济宁、莆田、湛江、绵阳、淮安、连云港、淄博、宜昌、邯郸、上饶、柳州、舟山、咸阳、九江、衡阳、威海、宁德、阜阳、株洲、丽水、南阳、襄阳、大庆、沧州、信阳、岳阳、商丘、肇庆、清远、滁州、龙岩、荆州、蚌埠、新乡、鞍山、湘潭、马鞍山、三明、潮州、梅州、秦皇岛、南平、吉林、安庆、泰安、宿迁、包头、郴州"
city_level4 = "韶关、常德、六安、汕尾、西宁、茂名、驻马店、邢台、南充、宜春、大理、丽江、延边、衢州、黔东南、景德镇、开封、红河、北海、黄冈、东营、怀化、阳江、菏泽、黔南、宿州、日照、黄石、周口、晋中、许昌、拉萨、锦州、佳木斯、淮南、抚州、营口、曲靖、齐齐哈尔、牡丹江、河源、德阳、邵阳、孝感、焦作、益阳、张家口、运城、大同、德州、玉林、榆林、平顶山、盘锦、渭南、安阳、铜仁、宣城、永州、黄山、西双版纳、十堰、宜宾、丹东、乐山、吉安、宝鸡、鄂尔多斯、铜陵、娄底、盘水、承德、保山、毕节、泸州、恩施、安顺、枣庄、聊城、百色、临汾、梧州、亳州、德宏、鹰潭、滨州、绥化、眉山、赤峰、咸宁"
city_level5 = "防城港、玉溪、呼伦贝尔、普洱、葫芦岛、楚雄、衡水、抚顺、钦州、四平、汉中、黔西南、内江、湘西、漯河、新余、延安、长治、文山、云浮、贵港、昭通、河池、达州、淮北、濮阳、通化、松原、通辽、广元、鄂州、凉山、张家界、荆门、来宾、忻州、克拉玛依、送宁、朝阳、崇左、辽阳、广安、萍乡、阜新、吕梁、池州、贺州、本溪、铁岭、自贡、锡林郭勒、白城、自山、雅安、酒泉、天水、晋城、巴彦淖尔、随州、兴安、临沧、鸡西、迪庆、攀枝花、鹤壁、黑河、双鸭山、三门峡、安康、乌兰察布、庆阳、伊犁、儋州、哈密、海西、甘孜、伊春、陇南、乌海、林芝、怒江、朔州、阳泉、嘉峪、关鹤岗、张掖、辽源、吴忠、昌吉、大兴安岭、巴音郭楞、阿坝、日喀则、阿拉善、巴中、平凉、阿克苏、定西、商洛、金昌、七台河、石嘴山、白银、铜川武威、吐鲁番、固原、山南、临夏、海东、喀什甘南、昌都、中卫、资阳、阿勒泰、塔城、博尔塔拉、海南、克孜、阿里、和田、玉树、那曲、黄南、海北、果洛、三沙"
list1 = city_level1.split("、")
list1_new = city_level1_new.split("、")
list2 = city_level2.split("、")
list3 = city_level3.split("、")
list4 = city_level4.split("、")
list5 = city_level5.split("、")
# 数据库连接配置
config = {
'host': 'localhost', # 数据库地址
'port': 3306, # 端口号
'user': 'root', # 数据库用户名
'password': '***', # 数据库密码
'db': 'data', # 数据库名称
'charset': 'utf8mb4', # 字符集
}
# 连接数据库
db = pymysql.connect(**config)
cursor = db.cursor()
获取数据并通过城市名称进行合并
df = pd.DataFrame(columns=('level', 'city'))
for i in list1:
df = df.append([{'level': '一线城市', 'city': i}], ignore_index=True)
for i in list1_new:
df = df.append([{'level': '新一线城市', 'city': i}], ignore_index=True)
for i in list2:
df = df.append([{'level': '二线城市', 'city': i}], ignore_index=True)
for i in list3:
df = df.append([{'level': '三线城市', 'city': i}], ignore_index=True)
for i in list4:
df = df.append([{'level': '四线城市', 'city': i}], ignore_index=True)
for i in list5:
df = df.append([{'level': '五线城市', 'city': i}], ignore_index=True)
df_data = pd.read_sql("select * from order_data", db)
def fun(x):
city = x.city.split("市")[0]
province = x.province.split("市")[0]
if city in df['city'].values.tolist():
return df[df['city'] == city]['level'].values[0]
elif province in df['city'].values.tolist():
return df[df['city'] == province]['level'].values[0]
else:
return np.NAN
df_data['city_level'] = df_data.apply(lambda x: fun(x), axis=1)
求取各城市等级的总消费金额和销售量,并计算出各城市等级占比情况
grouped_city_level = df_data.groupby('city_level').aggregate({'total_money': 'sum', 'item_num': 'sum'})
grouped_city_level['sum_money_city_level_rate'] = grouped_city_level['total_money'] / grouped_city_level['total_money'].sum()
grouped_city_level['sum_item_num_city_level_rate'] = grouped_city_level['item_num'] / grouped_city_level['item_num'].sum()
grouped_city_level = grouped_city_level.sort_values('sum_money_city_level_rate', ascending=False)
print(grouped_city_level)
结果如下
total_money item_num sum_money_city_level_rate \
city_level
五线城市 481855.0 1006 0.244877
三线城市 429984.0 890 0.218516
四线城市 413149.0 892 0.209961
新一线城市 246757.0 480 0.125401
二线城市 209388.0 424 0.106410
一线城市 186610.0 353 0.094835
sum_item_num_city_level_rate
city_level
五线城市 0.248702
三线城市 0.220025
四线城市 0.220519
新一线城市 0.118665
二线城市 0.104821
一线城市 0.087268
可以看出这个店铺在三四五线的销售更优于一二线城市,如果考虑线下销售打造品牌的话,可以优先选择城市等级低的城市作为开店选择。
RFM
获取数据库配置以及获取数据
import matplotlib.pyplot as plt
import pymysql as pymysql
import pandas as pd
import numpy as np
# 数据库连接配置
config = {
'host': 'localhost', # 数据库地址
'port': 3306, # 端口号
'user': 'root', # 数据库用户名
'password': '***', # 数据库密码
'db': 'data', # 数据库名称
'charset': 'utf8mb4', # 字符集
}
# 连接数据库
db = pymysql.connect(**config)
# 对新老客消费比进行分析
df = pd.read_sql(
"SELECT customer_name,left(order_create_time,10) as order_create_time,left(order_create_time,7) as month,1 AS order_num,cast(total_money as float ) AS total_money FROM order_data WHERE order_status = '交易成功'",
db)
df['order_create_time'] = pd.to_datetime(df['order_create_time'], format='%Y%m%d ')
df.sort_values('order_create_time', inplace=True)
通过重复订单数据来判断这批订单的新老客
# 查看列customer_name的重复数据数量
duplicates_in_customer_name = df['customer_name'].value_counts()
# 找到重复的数据(即出现次数大于1的数据)
duplicates = duplicates_in_customer_name[duplicates_in_customer_name > 1]
duplicates_no = duplicates_in_customer_name[duplicates_in_customer_name == 1]
print(df)
print(len(duplicates))
print(len(duplicates_no))
结果如下
customer_name order_create_time month order_num total_money
39 幻走外贸男装服饰 2022-01-02 2022-01 1 1965.0
255 拾心少女。 2022-01-05 2022-01 1 4314.0
386 诗呓 2022-01-07 2022-01 1 1501.0
316 你型我溯 2022-01-07 2022-01 1 2750.0
351 莎臣豹 2022-01-09 2022-01 1 4409.0
.. ... ... ... ... ...
25 斯文g | 斯文m 2023-12-25 2023-12 1 3110.0
287 神都偏爱 2023-12-26 2023-12 1 542.0
105 神都偏爱 2023-12-27 2023-12 1 1644.0
96 悃悃鋩鋩 2023-12-29 2023-12 1 776.0
18 最初的梦想 2023-12-30 2023-12 1 824.0
[507 rows x 5 columns]
142
101
可以看出新老客占比2:3
将数据转化为RFM数据
# RFM
# R(Recency):指客户最近一次消费的时间间隔。这个指标主要反映了客户的活跃程度,R值越小,表示客户的活跃程度越高;R值越大,则表示客户上一次交易的时间越久远,可能面临流失的风险。
# F(Frequency):指客户在最近一段时间内消费的次数。F值越大,说明客户消费频次越高,越活跃。
# M(Monetary):指客户在最近一段时间内消费的金额。M值越高,则客户消费金额越大,可能属于高价值或VIP客户。
rfm = df.pivot_table(index='customer_name',
values=['order_num', 'total_money', 'order_create_time'],
aggfunc={'order_create_time': 'max',
'total_money': 'sum',
'order_num': 'sum'})
# 到最后一个订单时间距离天数 = R
rfm['R'] = (rfm['order_create_time'].max() - rfm['order_create_time']) / np.timedelta64(1, 'D')
rfm.rename(columns={'order_num': 'F', 'total_money': 'M'}, inplace=True)
rfm[['R', 'F', 'M']].apply(lambda x: x - x.mean())
rfm.reset_index(inplace=True)
data = rfm[['customer_name', 'F', 'M', 'R']]
print(data)
结果如下
customer_name F M R
0 mrゞ唯嗳ガ舞 2 2562.0 225.0
1 ゝ柒彩红艹 3 7207.0 630.0
2 ヾ左岸烟逝 3 6514.0 306.0
3 与我相关 3 3845.0 219.0
4 丫頭╃片子 1 4720.0 128.0
.. ... .. ... ...
238 骨子里的高雅 3 11281.0 316.0
239 魔夜战队 2 2904.0 152.0
240 鼻尖触碰 3 6901.0 96.0
241 ︶ㄣ血゛泪Oo 2 3666.0 363.0
242 Johnny.R。 3 7824.0 374.0
[243 rows x 4 columns]
将RFM转化为用户评定
def rfm_func(x):
level = x.apply(lambda x: '1' if x > 0 else '0')
label = level.R + level.F + level.M
d = {
'111': '重要价值客户',
'011': '重要保持客户',
'101': '重要发展客户',
'001': '重要挽留客户',
'110': '一般价值客户',
'010': '一般保持客户',
'100': '一般发展客户',
'000': '一般挽留客户',
}
result = d[label]
return result
rfm['label'] = rfm[['R', 'F', 'M']].apply(lambda x: x - x.mean()).apply(rfm_func, axis=1)
rfm.reset_index(level=0, inplace=True)
print(rfm)
结果如下
index customer_name order_create_time F M R label
0 0 mrゞ唯嗳ガ舞 2023-05-19 2 2562.0 225.0 一般挽留客户
1 1 ゝ柒彩红艹 2022-04-09 3 7207.0 630.0 重要价值客户
2 2 ヾ左岸烟逝 2023-02-27 3 6514.0 306.0 重要价值客户
3 3 与我相关 2023-05-25 3 3845.0 219.0 一般保持客户
4 4 丫頭╃片子 2023-08-24 1 4720.0 128.0 一般挽留客户
.. ... ... ... .. ... ... ...
238 238 骨子里的高雅 2023-02-17 3 11281.0 316.0 重要价值客户
239 239 魔夜战队 2023-07-31 2 2904.0 152.0 一般挽留客户
240 240 鼻尖触碰 2023-09-25 3 6901.0 96.0 重要保持客户
241 241 ︶ㄣ血゛泪Oo 2023-01-01 2 3666.0 363.0 一般发展客户
242 242 Johnny.R。 2022-12-21 3 7824.0 374.0 重要价值客户
[243 rows x 7 columns]
绘制散点图
# 对重要价值客户及非重要价值客户的消费时间、消费频率绘制散点图
rfm.loc[rfm.label == '重要价值客户', 'color'] = 'g'
rfm.loc[~(rfm.label == '重要价值客户'), 'color'] = 'r'
rfm.plot.scatter('F', 'R', c=rfm.color)
rmf_group = rfm.groupby('label').agg({'F': 'mean', 'M': 'mean', 'R': 'mean'})
rmf_group.reset_index(level=0, inplace=True)
print(rmf_group)
plt.show()
结果如下
label F M R
0 一般价值客户 3.000000 4283.000000 495.000000
1 一般保持客户 3.333333 4384.333333 151.333333
2 一般发展客户 1.196970 2756.424242 489.060606
3 一般挽留客户 1.351351 2907.716216 134.891892
4 重要价值客户 3.136364 9010.500000 383.000000
5 重要保持客户 4.250000 11323.850000 100.400000
6 重要发展客户 2.000000 7038.900000 441.600000
7 重要挽留客户 2.000000 6819.760000 122.080000
可以看出重要客户的最后一次购买R平均都超过了100天,甚至有超过一年的,所以客户召回以及挽留动作需要加强,增加客户黏性,重点可以关注保持和价值的客户,这些客户能带来重大的利益。
可以看出重要价值的客户平均购买频次在3-4次之间,最后一次购买平均为383天,重要客户的最后一次购买R平均都超过了100天,所以客户召回以及挽留动作需要加强,增加客户黏性,重点可以关注保持和价值的客户,这些客户能带来重大的利益。