用Python分析用户消费行为 Student Comsumption Analysis②

** 【续上篇】 用Python分析用户消费行为 Student Comsumption Analysis ① https://blog.csdn.net/weixin_44216391/article/details/89309643

#  【续】本次案例:用户消费行为分析

# 借用阿里天池【数智教育_数据可视化创新大赛】数据源中的学生消费数据来作为本次用户消费行为分析的数据来源。

# 本篇分析方法参考该帖子:http://www.woshipm.com/data-analysis/757648.html
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime
%matplotlib inline
plt.style.use("ggplot")
import warnings
warnings.filterwarnings("ignore")

# plt.rcParams['font.sans-serif']=['SimHei']   # 用来正常显示中文标签
# plt.rcParams['axes.unicode_minus']=False   # 用来正常显示负号

# seaborn中文乱码解决方案
import seaborn as sns
from matplotlib.font_manager import FontProperties
myfont=FontProperties(fname=r'C:\Windows\Fonts\simhei.ttf',size=14)
sns.set(font=myfont.get_name())
df=pd.read_csv("D:/2018_BigData/Python/Python_files_Notebook/theme_practice/student_consumption_day.csv")
df.head()
DealTimebf_StudentIDAccNamePerSexMonDealavgMonDealtransaction_timesmonth
02018-07-0113983裘某某-3.7-3.7012018-07-01
12018-07-0114018虞某某-9.5-9.5012018-07-01
22018-07-0114073刘某某-8.0-8.0012018-07-01
32018-07-0114074周某某-14.3-7.1522018-07-01
42018-07-0114097毛某某-10.0-10.0012018-07-01
# 承接上一章,将用户消费数据进行数据透视。

pivoted_counts=df.pivot_table(index="bf_StudentID",columns="month",values="transaction_times",aggfunc="sum").fillna(0)
columns_month=df.month.sort_values().astype("str").unique()
pivoted_counts.columns=columns_month
pivoted_counts.head()
2018-07-012018-08-012018-09-012018-10-012018-11-012018-12-012019-01-01
bf_StudentID
130120.00.010.010.015.07.09.0
1356417.025.082.063.069.075.050.0
135998.010.039.033.034.031.026.0
136858.012.028.033.034.039.017.0
139479.022.081.064.066.072.063.0
# 用applymap+lambda转换数据,只要当月消费超过30次,记为1,反之为0。
pivoted_purchase = pivoted_counts.applymap(lambda x: 1 if x > 30 else 0)
pivoted_purchase.head()
2018-07-012018-08-012018-09-012018-10-012018-11-012018-12-012019-01-01
bf_StudentID
130120000000
135640011111
135990011110
136850001110
139470011111
# 原帖子:

# 接下来进行用户分层,我们按照用户的消费行为,简单划分成几个维度:新用户、活跃用户、不活跃用户、回流用户。
# 新用户的定义是第一次消费。活跃用户即老客,在某一个时间窗口内有过消费。不活跃用户则是时间窗口内没有消费过的老客。回流用户是在上一个窗口中没有消费,而在当前时间窗口内有过消费。以上的时间窗口都是按月统计。
# 比如某用户在1月第一次消费,那么他在1月的分层就是新用户;他在2月消费国,则是活跃用户;3月没有消费,此时是不活跃用户;4月再次消费,此时是回流用户,5月还是消费,是活跃用户。
# 分层会涉及到比较复杂的逻辑判断。

# ----------------------------------------------------------

# 本次学生消费案例:

# 接下来进行用户分层,我们按照学生的消费行为,简单划分成几个维度:新生(新用户)、超爱饭堂的学生(活跃用户)、不爱饭堂的学生(不活跃用户)、偶尔爱饭堂的学生(回流用户)。
# 新生的定义是第一次消费超过30次。超爱饭堂的学生即常客/老客,在某一个时间窗口内有过消费30次以上。不爱饭堂的学生则是时间窗口内没有消费超过30次的老客。回流用户是在上一个窗口中没有消费30次以上,而在当前时间窗口内有过消费30次以上。以上的时间窗口都是按月统计。

# 比如某学生在9月第一次消费30次以上,那么他在9月的分层就是新生(新用户);
# 他在10月消费30次以上,则是超爱饭堂的学生(活跃用户);
# 11月没有消费30次以上,此时是不爱饭堂的学生(不活跃用户);
# 12月再次消费30次以上,此时是偶尔爱饭堂的学生(回流用户);
# 1月还是消费30次以上,是超爱饭堂的学生(活跃用户)。

# 分层会涉及到比较复杂的逻辑判断。
def active_status(data):
    status=[]
    for i in range(7):
        
        # 若本月没有“消费30次以上”
        if data[i] == 0:
            if len(status) > 0:
                if status[i-1] == "unreg":
                    status.append("unreg")
                else:
                    status.append("unlike_canteen")
            else:
                status.append("unreg")
        # 若本月“消费30次以上”
        else:
            if len(status) == 0:
                status.append("new")
            else:
                if status[i-1] == "unlike_canteen":
                    status.append("occasionally_like_canteen")
                elif status[i-1] == "unreg":
                    status.append("new")
                else:
                    status.append("love_canteen")
    return status
                
# 函数写得比较复杂,主要分为两部分的判断,以本月是否“消费60次以上”为界。
# 本月没有“消费60次以上”,还要额外判断他是不是新生(新客),
# 因为部分学生是9月份才“消费30次以上”成为新生(新客),那么在7、8月份他应该连新生(新客)都不是,用unreg表示。
# 如果是老客(即前一个月消费30次以上),则为unlike_canteen。

# 本月若有“月消费30次以上”,需要判断是不是第一次“月消费30次以上”,上一个时间窗口有没有“月消费30次以上”。
# 大家可以多调试几次理顺里面的逻辑关系,对用户进行分层,逻辑确实不会简单,而且这里只是简化版本的。
# pivoted_purchase_status = pivoted_purchase.apply(lambda x: active_status(x),axis = 1)
# pivoted_purchase_status.head()

#  运行报错:("'Series' object is not callable", 'occurred at index 13012')
# 那查看一下“index 13012”是何方神圣。
pivoted_purchase.loc[13012]
2018-07-01    0
2018-08-01    0
2018-09-01    0
2018-10-01    0
2018-11-01    0
2018-12-01    0
2019-01-01    0
Name: 13012, dtype: int64
# 看看其他行与索引13012有没有什么区别
pivoted_purchase.loc[13564]

# 没差别。。。
2018-07-01    0
2018-08-01    0
2018-09-01    1
2018-10-01    1
2018-11-01    1
2018-12-01    1
2019-01-01    1
Name: 13564, dtype: int64
pivoted_purchase.shape

# 1730行(学生学号),7列(月份)
(1730, 7)
pivoted_purchase.dtypes
2018-07-01    int64
2018-08-01    int64
2018-09-01    int64
2018-10-01    int64
2018-11-01    int64
2018-12-01    int64
2019-01-01    int64
dtype: object
type(pivoted_purchase)
pandas.core.frame.DataFrame
# 这么看来,前面报错(pivoted_purchase.shape)是因为,series不能用于apply函数?
# 搜索无果。

# 回到报错提示去看详情,终于发现了点东东。

# 先看详情:

# <ipython-input-31-17b35956730f> in <lambda>(x)
# ----> 1 pivoted_purchase_status = pivoted_purchase.apply(lambda x: active_status(x),axis = 1)
#       2 pivoted_purchase_status.head()
#       3 
#       4 #  运行报错:("'Series' object is not callable", 'occurred at index 13012')

# <ipython-input-28-331c5573a3a9> in active_status(data)
#       4 
#       5         # 若本月没有“消费30次以上”
# ----> 6         if data(i) < 30:
#       7             if len(status) > 0:
#       8                 if status[i-1] == "unreg":

# TypeError: ("'Series' object is not callable", 'occurred at index 13012')

# -----------------------------------------------------------------

# 以上,首先提示错误在<lambda>(x);
# 接下来提示错误在active_status(data)的第六行:
# ----> 6         if data(i) < 30:
# 缩小到行了,再一细看,果然发现了一个小括号data(i)。
# 而根据多出查询说明,报错"'XXX' object is not callable"一般是因为该用中括号的地方用了小括号。
# 回到原参考帖去看,果然人家是中括号啊~~~字太小了看不清,认错敲错了。。。
pivoted_purchase_status = pivoted_purchase.apply(lambda x: active_status(x),axis = 1)
pivoted_purchase_status.head(8)

# 一开始虽然没运行报错,不过又转化成坑爹的不知道是啥的格式了。
# 而且貌似逻辑不对。显示结果居然全是学生饭堂深度爱好者(love_canteen),这不科学。

# 然后又细细查看,原来是把for i in range(7)后面的if data[i]==0 的“0”误写成“30”了。
# 将if data[i]==30更正回if data[i]==0之后,逻辑正常,如下所示。只是格式问题还需搜索方法来处理。
bf_StudentID
13012    [unreg, unreg, unreg, unreg, unreg, unreg, unreg]
13564    [unreg, unreg, new, love_canteen, love_canteen...
13599    [unreg, unreg, new, love_canteen, love_canteen...
13685    [unreg, unreg, unreg, new, love_canteen, love_...
13947    [unreg, unreg, new, love_canteen, love_canteen...
13948    [unreg, unreg, new, love_canteen, love_canteen...
13949    [unreg, unreg, new, love_canteen, love_canteen...
13950    [unreg, unreg, new, love_canteen, love_canteen...
dtype: object
# 仍然试试,使用索引1来表示第二列[1]。

# month=pivoted_purchase_status[1].str.split(',',expand=True)
# month.columns=['201807','201808','201809','201810','201811','201812','201901']
# pivoted_purchase_status=pivoted_purchase_status.join(month)
# pivoted_purchase_status.head()

# 运行报错:KeyError: 1
# 那接下来看看要怎么确定列名、转化格式、完成分列和重建dataframe。
# 先看看现在都是些什么格式、什么列名。

type(pivoted_purchase_status)

# def和apply函数搞一下,
# 从pivoted_purchase的pandas.core.frame.DataFrame到了pivoted_purchase_status的pandas.core.series.Series。
pandas.core.series.Series
# pivoted_purchase_status.columns

# AttributeError: 'Series' object has no attribute 'columns'
# 原来是series格式。
# 那如何转成dataframe呢?来战。
pivoted_purchase_status=pd.DataFrame(pivoted_purchase_status)
# 或 pivoted_purchase_status.reset_index()
pivoted_purchase_status.head()
0
bf_StudentID
13012[unreg, unreg, unreg, unreg, unreg, unreg, unreg]
13564[unreg, unreg, new, love_canteen, love_canteen...
13599[unreg, unreg, new, love_canteen, love_canteen...
13685[unreg, unreg, unreg, new, love_canteen, love_...
13947[unreg, unreg, new, love_canteen, love_canteen...
type(pivoted_purchase_status)

# 看起来像是成功转化成DataFrame了
pandas.core.frame.DataFrame
pivoted_purchase_status.columns

# 稀奇古怪的RangeIndex,是啥。。。能否分列?Excel好办,不过Python…应该更简单吧。。
# 问题是,没有列名,何来分列。。。
RangeIndex(start=0, stop=1, step=1)
pivoted_purchase_status.index
Int64Index([13012, 13564, 13599, 13685, 13947, 13948, 13949, 13950, 13951,
            13952,
            ...
            16153, 16154, 16155, 16156, 16157, 16158, 16159, 16160, 16161,
            16162],
           dtype='int64', name='bf_StudentID', length=1730)
# 重置行索引为默认索引,生成新的Dataframe——reset_index()函数
pivoted_purchase_status=pivoted_purchase_status.reset_index()
pivoted_purchase_status.head()
bf_StudentID0
013012[unreg, unreg, unreg, unreg, unreg, unreg, unreg]
113564[unreg, unreg, new, love_canteen, love_canteen...
213599[unreg, unreg, new, love_canteen, love_canteen...
313685[unreg, unreg, unreg, new, love_canteen, love_...
413947[unreg, unreg, new, love_canteen, love_canteen...
pivoted_purchase_status.columns

# 出现列了。只是列名是一个数字0,那该如何?直接用0还是可以重新赋值?
Index(['bf_StudentID', 0], dtype='object')
# 先直接用列[0]试试.

# month=pivoted_purchase_status[0].str.split(',',expand=True)
# month.columns=['201807','201808','201809','201810','201811','201812','201901']
# pivoted_purchase_status=pivoted_purchase_status.join(month)
# pivoted_purchase_status.head()

# 运行报错 ValueError: Length mismatch: Expected axis has 1 elements, new values have 7 elements
# 报错出现在:第二行month.columns赋值那里。可能是太多参数了,那我们减成一个参数看看。
month=pivoted_purchase_status[0].str.split(',',expand=True)
month.columns=['month']
pivoted_purchase_status=pivoted_purchase_status.join(month)
pivoted_purchase_status.head()

# 用month.columns=['month']替换month.columns=['201807','201808','201809','201810','201811','201812','201901']
# 运行没有报错,只是得出的结果,稀奇古怪,说明其实没有将列中内容分开。可能是分列符号没用对?
# 不过起码可以证明,不需要重新给列名赋值,直接用[0]索引即可。
bf_StudentID0month
013012[unreg, unreg, unreg, unreg, unreg, unreg, unreg]NaN
113564[unreg, unreg, new, love_canteen, love_canteen...NaN
213599[unreg, unreg, new, love_canteen, love_canteen...NaN
313685[unreg, unreg, unreg, new, love_canteen, love_...NaN
413947[unreg, unreg, new, love_canteen, love_canteen...NaN
# 尝试删掉month.columns赋值语句,运行,

# month=pivoted_purchase_status[0].str.split(', ',expand=True)
# pivoted_purchase_status=pivoted_purchase_status.join(month)
# pivoted_purchase_status.head()

# 运行报错 ValueError: columns overlap but no suffix specified: Index([0], dtype='object')
# 报错在第二行:pivoted_purchase_status=pivoted_purchase_status.join(month)
pd.concat([pivoted_purchase_status,pivoted_purchase_status[0].str.split(',',expand=True)],axis=1).head(2)

# 尝试用concat替代join来拼接,还是没成功。估计问题不在join或者concat,而是在split函数里。
# 又或者,一个大胆的猜想:split函数中的列索引[0],并不是索引第二列,而是索引第一列bf_StudentID。这个认知十分震惊。
# 那我们重新给列赋值新列名,用列名来索引。
bf_StudentID0month0
013012[unreg, unreg, unreg, unreg, unreg, unreg, unreg]NaNNaN
113564[unreg, unreg, new, love_canteen, love_canteen...NaNNaN
pivoted_purchase_status = pivoted_purchase_status.drop(["month"],axis=1)   #删掉刚刚多出的month列,恢复纯净
pivoted_purchase_status.columns = ["bf_StudentID","User_Hierarchy"]
pivoted_purchase_status.head(2)

# 更改列名成功。刚刚猜想正确,列索引[0]果然表示第一列,而不是列名为0的第二列。
bf_StudentIDUser_Hierarchy
013012[unreg, unreg, unreg, unreg, unreg, unreg, unreg]
113564[unreg, unreg, new, love_canteen, love_canteen...
pd.concat([pivoted_purchase_status,pivoted_purchase_status["User_Hierarchy"].str.split(',',expand=True)],axis=1).head(2)
# 这厉害,还是分不开。。。
bf_StudentIDUser_Hierarchy0
013012[unreg, unreg, unreg, unreg, unreg, unreg, unreg]NaN
113564[unreg, unreg, new, love_canteen, love_canteen...NaN
a=pivoted_purchase_status.head(5)
a
bf_StudentIDUser_Hierarchy
013012[unreg, unreg, unreg, unreg, unreg, unreg, unreg]
113564[unreg, unreg, new, love_canteen, love_canteen...
213599[unreg, unreg, new, love_canteen, love_canteen...
313685[unreg, unreg, unreg, new, love_canteen, love_...
413947[unreg, unreg, new, love_canteen, love_canteen...
a["User_Hierarchy"].str.split((" "),expand=True)
# 仍然失败。
0
0NaN
1NaN
2NaN
3NaN
4NaN
# 导出到CSV,用Excel处理完再导回来。
pivoted_purchase_status.to_csv(r"D:/2018_BigData/Python/Python_files_Notebook/Theme_Practice/student_consumption_pivoted_purchase_status.csv",index=False)
# Excel已处理完,现在导回来。

pivoted_purchase_status=pd.read_csv("D:/2018_BigData/Python/Python_files_Notebook/theme_practice/student_consumption_pivoted_purchase_status_splited_above30per month.csv")
pivoted_purchase_status.tail(4)

# 妥。
bf_StudentIDUser_HierarchyUnnamed: 2Unnamed: 3Unnamed: 4Unnamed: 5Unnamed: 6Unnamed: 7
172616159unregunregnewlove_canteenlove_canteenlove_canteenlove_canteen
172716160unregunregnewlove_canteenlove_canteenlove_canteenunlike_canteen
172816161unregunregnewlove_canteenlove_canteenlove_canteenlove_canteen
172916162unregunregnewlove_canteenlove_canteenlove_canteenunlike_canteen
# 更换列名

columns=["bf_StudentID",'201807','201808','201809','201810','201811','201812','201901']
pivoted_purchase_status.columns=columns
pivoted_purchase_status.tail(4)

# 妥。
bf_StudentID201807201808201809201810201811201812201901
172616159unregunregnewlove_canteenlove_canteenlove_canteenlove_canteen
172716160unregunregnewlove_canteenlove_canteenlove_canteenunlike_canteen
172816161unregunregnewlove_canteenlove_canteenlove_canteenlove_canteen
172916162unregunregnewlove_canteenlove_canteenlove_canteenunlike_canteen
# 从结果看,学生(用户)每个月的分层状态以及变化已经被我们计算出来。我是根据透视出的宽表计算。
# 其实还有一种另外一种写法,只提取时间窗口内的数据和上个窗口对比判断,封装成函数做循环,它适合ETL的增量更新。
purchase_status_counts = pivoted_purchase_status.replace("unreg",np.NaN).apply(lambda x: pd.value_counts(x))
purchase_status_counts.tail(10)
# unreg状态排除掉,它是「未来」才作为新生(新客),这么能计数呢。换算成不同分层每月的统计量。
bf_StudentID201807201808201809201810201811201812201901
149381.0NaNNaNNaNNaNNaNNaNNaN
149401.0NaNNaNNaNNaNNaNNaNNaN
149421.0NaNNaNNaNNaNNaNNaNNaN
149441.0NaNNaNNaNNaNNaNNaNNaN
149461.0NaNNaNNaNNaNNaNNaNNaN
143371.0NaNNaNNaNNaNNaNNaNNaN
newNaNNaN7.01498.031.033.011.02.0
love_canteenNaNNaNNaN7.01345.01324.01350.01120.0
unlike_canteenNaNNaNNaNNaN160.0124.0171.0433.0
occasionally_like_canteenNaNNaNNaNNaNNaN88.048.027.0
# 换算成不同分层每月的统计量。

purchase_status_counts=purchase_status_counts.tail(4)
purchase_status_counts

# 明显9月开学新生人数众多,高达近一千五百人。8月也有学生回校,但仅有7人。
# 然后,1300以上同学,9-12月都超爱饭堂啊哈哈。
# 不过呢,有些同学,每个月都有一百多位同学,经常往校外跑,常常不在学校吃饭。
# 这些常常往校外跑的同学,要重点关注,是不是去干坏事了哈哈(此处开个玩笑)
bf_StudentID201807201808201809201810201811201812201901
newNaNNaN7.01498.031.033.011.02.0
love_canteenNaNNaNNaN7.01345.01324.01350.01120.0
unlike_canteenNaNNaNNaNNaN160.0124.0171.0433.0
occasionally_like_canteenNaNNaNNaNNaNNaN88.048.027.0
# 生成面积图,比较丑。
# 因为它只是某时间段消费过的用户的后续行为,蓝色(新生)和绿色区域(不爱饭堂)都可以不看。
# 只看红色回流(偶尔爱饭堂)和橙色活跃(超爱饭堂)这两个分层,学生数比较稳定。
# 这两个分层相加,就是消费用户占比(后期没新生/新客)。

purchase_status_counts.fillna(0).T.plot.area(figsize=(12,6))
plt.show()

# 其实这里多了一组x值(学生ID),不过反正显示0,不影响分析,先不管。后面再删除学生ID列。

在这里插入图片描述
png

# 原帖子没有本节fillna(0)赋值代码及删除学生ID列,导致“各月学生偶尔爱饭堂的占比”出图趋势不科学。
# 所以在这里提前加多两行语句赋空值。然后结果正确。
purchase_status_counts = purchase_status_counts.fillna(0).drop(["bf_StudentID"],axis=1)
purchase_status_counts
201807201808201809201810201811201812201901
new0.07.01498.031.033.011.02.0
love_canteen0.00.07.01345.01324.01350.01120.0
unlike_canteen0.00.00.0160.0124.0171.0433.0
occasionally_like_canteen0.00.00.00.088.048.027.0
return_rata = purchase_status_counts.apply(lambda x: x/x.sum(),axis=0)
return_rata

# 原贴的apply函数中 axis参数是1(即横向求和),感觉结果不科学;
# 所以改为axis=0(纵向求和),结果接近科学。
201807201808201809201810201811201812201901
newNaN1.00.9953490.0201820.0210330.0069620.001264
love_canteenNaN0.00.0046510.8756510.8438500.8544300.707965
unlike_canteenNaN0.00.0000000.1041670.0790310.1082280.273704
occasionally_like_canteenNaN0.00.0000000.0000000.0560870.0303800.017067
return_rata1 = return_rata.apply(lambda x: x*100,axis=0)
return_rata1.loc["occasionally_like_canteen"].plot(figsize = (12,6))
plt.xlabel('时间(月)', fontsize=18) 
plt.ylabel('百分比(%)', fontsize=18) 
plt.title('各月偶尔爱饭堂学生的占比', fontsize=18)

plt.show()

# 原贴分析:
# 用户回流占比在5%~8%,有下降趋势。所谓回流占比,就是回流用户在总用户中的占比。
# 另外一种指标叫回流率,指上个月多少不活跃/消费用户在本月活跃/消费。
# 因为不活跃的用户总量近似不变,所以这里的回流率也近似回流占比。

# 本学生消费案例分析:
# 学生偶尔爱饭堂占比从7-9月一直为0,因为学生尚未回校。在11月达到峰值,接近5%,数量也不多,不到一百人。
# 也就是说,即便是峰值11月到春节的1月,每个月只有不到100的学生,是:之前在饭堂有过“月消费30次以上”,然后又比较少在饭堂吃饭(“月消费没有30次”),最后在当月又回饭堂消费30次以上。

在这里插入图片描述
png

return_rata2 = return_rata.apply(lambda x: x*100,axis=0)
return_rata2.loc["love_canteen"].plot(figsize = (12,6))
plt.xlabel('时间(月)', fontsize=18) 
plt.ylabel('百分比(%)', fontsize=18) 
plt.title('各月超爱饭堂学生的比例', fontsize=18)

plt.show()

# 原贴分析:
# 活跃用户的下降趋势更明显,占比在3%~5%间。这里用户活跃可以看作连续消费用户,质量在一定程度上高于回流用户。
# 结合回流用户和活跃用户看,在后期的消费用户中,60%是回流用户,40%是活跃用户/连续消费用户,整体质量还好。
# 但是针对这两个分层依旧有改进的空间,可以继续细化数据。
# -------------------------
# 本次学生消费案例分析:
# 超爱饭堂学生自9月开学,10月开始习惯学校并开始爱上学校饭堂之后,一直到1月春节离校返家,占比在70%-90%之间。
# 这里超爱饭堂学生可以看做连续消费用户,爱校爱饭堂程度(即原贴的"质量")在一定程度上高于偶尔爱饭堂的学生。
# 结合偶尔爱饭堂的学生和超爱饭堂的学生看,在后期的学校消费用户中,93%是超爱饭堂学生,7%是偶尔爱饭堂的学生/连续消费用户。
# 从以上两个学生用户层次看,整体质量非常好,几乎全是超爱饭堂的学生,连续消费诶。
# 但是针对“不爱饭堂的学生”这个分层,依旧有改进的空间,可以继续细化数据。

在这里插入图片描述
png

# 接下来分析用户质量,因为消费行为有明显的二八倾向,我们需要知道高质量用户(高额饭堂消费学生)为消费贡献了多少份额。
df.head(1)
DealTimebf_StudentIDAccNamePerSexMonDealavgMonDealtransaction_timesmonth
02018-07-0113983裘某某-3.7-3.712018-07-01
# user_amount = df.groupby("bf_StudentID").MonDeal.sum().sort_values().reset_index()
# user_amount["amount_cumsum"]=user_amount.MonDeal.cumsum()
# user_amount.tail()

# 原贴代码如上,为了方便一步步阅览,下面我们分开运行和分步展示。
user_amount = df.groupby("bf_StudentID").MonDeal.sum().sort_values().reset_index()
user_amount.head(3)

# 消费总金额前三。
bf_StudentIDMonDeal
015556-6239.95
114123-5698.20
216024-5125.69
user_amount["amount_cumsum"]=user_amount.MonDeal.cumsum()
user_amount.tail()

# 原贴:
# 新建一个对象,按用户的消费金额生序。使用cumsum,它是累加函数。
# 逐行计算累计的金额,最后的3883740便是总消费额。

# 本学生消费案例:
# 所有学生累计7个月总贡献消费金额388万。
# 系统数据显示为负数,为了方便后面画图展示,下面我们将负数变正数。
bf_StudentIDMonDealamount_cumsum
172514892-32.7-3883701.02
172613956-11.0-3883712.02
172714546-10.0-3883722.02
172814363-9.5-3883731.52
172913967-9.0-3883740.52
user_amount["MonDeal"]=user_amount["MonDeal"]*(-1)
user_amount["amount_cumsum"]=user_amount["amount_cumsum"]*(-1)
# 此语句备用 user_amount["MonDeal","amount_cumsum"],或可思考:上述两句相似运算赋值语句是否可以合并成一句?
user_amount.tail()
bf_StudentIDMonDealamount_cumsum
17251489232.73883701.02
17261395611.03883712.02
17271454610.03883722.02
1728143639.53883731.52
1729139679.03883740.52
# amount_total=user_amount.amount_cumsum.max()
# user_amount["prop"]=user_amount.apply(lambda x:x.amount_cumsum/amount_total,axis=1)
# user_amount.tail()

# 下面将上述语句逐句分拆展示,方便理解。
amount_total=user_amount.amount_cumsum.max()
amount_total
3883740.520000005
user_amount["prop"]=user_amount.apply(lambda x:x.amount_cumsum/amount_total,axis=1)
user_amount.tail()

# 转换成百分比。
bf_StudentIDMonDealamount_cumsumprop
17251489232.73883701.020.999990
17261395611.03883712.020.999993
17271454610.03883722.020.999995
1728143639.53883731.520.999998
1729139679.03883740.521.000000
user_amount.prop.plot()
plt.show()

# 绘制趋势图,横坐标是按贡献金额大小排序而成,纵坐标则是用户累计贡献。
# 可以很清楚的看到,差点呈线性分布,反而并没有呈现出著名的“二八定律”。
# 或许,因为学校学生消费是非市场经济,所以,此处二八定律失效。

在这里插入图片描述
png

plt.figure(figsize=(12,4))

plt.subplot(121)
df.groupby("bf_StudentID").MonDeal.sum().hist(bins=30)
plt.xlabel("学生消费总金额",fontsize=15)
plt.title("2018.7-2019.1学生消费总金额分布",fontsize=18)

plt.subplot(122)
df.groupby("bf_StudentID").transaction_times.sum().hist(bins=30)
plt.ylabel("学生消费总次数",fontsize=15)
plt.title("2018.7-2019.1学生消费总次数分布",fontsize=18)

plt.show()

# 从下面每段区间样本数可计算,消费1800~3200之间人数1083人,占总人数60%
# [97 159 153 156 163 181 164 110]

在这里插入图片描述
png

MonDealSum = df.groupby("bf_StudentID").MonDeal.sum().reset_index() 
x1 = MonDealSum["MonDeal"]
counts, bin_edges = np.histogram(x1, bins=30)
print(counts)

#只需要简单的计算每段区间的样本数,而并不想画图显示它们,那么可以直接用np.histogram()
[  1   0   1   0   0   2   3   6   8   8  11  34  39  59  76  97 159 153
 156 163 181 164 110  87  61  70  29  20  11  21]
# 前面知道前60%的学生消费金额区间在1800~3200之间,所以,
# 想对user_amount数据进行区间求和,获得1800<x<3200的x的和
user_amount.head(2)
bf_StudentIDMonDealamount_cumsumprop
0155566239.956239.950.001607
1141235698.2011938.150.003074
# 1730位学生,前20%是346位同学,看看消费前346位同学的总消费金额。
array = user_amount["MonDeal"].astype(int)
np.sum(array[1:346], axis=0)

#接近119万,占比学生总额388万的30%,确实不符合二八原则。
1188167
# 再看消费金额从高到低排列,排位20%到80%的总消费金额占比。
# [20%:80%],[347,1384]

np.sum(array[347:1384], axis=0)

# 这60%的同学,总消费金额232万,占总金额388万的60%。。。
# 没有二八原则,更是十分贴近线性规律。
2320956
print(user_amount.loc[346].astype(int))
print("")
print(user_amount.loc[1384].astype(int))

# 第346名同学消费2913元,第1384名同学消费1572元,
# 与我们刚刚直方图得出的金额聚集区间[1800,3200]有些出入,所以接下来修正一下看看。
bf_StudentID       16123
MonDeal             2913
amount_cumsum    1197484
prop                   0
Name: 346, dtype: int32

bf_StudentID       15971
MonDeal             1572
amount_cumsum    3520532
prop                   0
Name: 1384, dtype: int32
print(user_amount.loc[210].astype(int))
print("")
print(user_amount.loc[1220].astype(int))

# 比较接近。
# 从第210位同学(消费3216元),到第1220位同学(消费1798元),累计总消费金额246万(324万-78万)。
# 即,1010名同学(人数占比近60%),累计总消费金额占比246/388=63%。
# 连占比最高的区间,金额加总也接近1:1的线性比例,真的说明这个消费人群,没有二八原则的基因。。。
bf_StudentID      15901
MonDeal            3216
amount_cumsum    781881
prop                  0
Name: 210, dtype: int32

bf_StudentID       14873
MonDeal             1798
amount_cumsum    3242862
prop                   0
Name: 1220, dtype: int32
# 既然多次证明这个消费人群没有二八原则的基因,
# 那如果继续将其当做市场消费者来分析,可能意义不大。
# 所以,本次学生消费行为分析到此结束。
# 至于更深层次的用户行为分析,待下次遇到真实市场中的消费数据时再另行分析。
# 手痒,试试Python的几个小技能。可以结束了。

# 试试np.sum()某一列求和。
array = user_amount["MonDeal"]
np.sum(array, axis=0)
3883740.52
# 试试np.sum()所有列求和。
np.sum(user_amount, axis=0).astype(int)
bf_StudentID       25968788
MonDeal             3883740
amount_cumsum   -2147483648
prop                   1050
dtype: int32

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值