首先,源数据前五行是这样的。
DealTime | bf_StudentID | AccName | PerSex | MonDeal | avgMonDeal | transaction_times | month | |
---|---|---|---|---|---|---|---|---|
0 | 2018-07-01 | 13983 | 裘某某 | 男 | -3.7 | -3.70 | 1 | 2018-07-01 |
1 | 2018-07-01 | 14018 | 虞某某 | 男 | -9.5 | -9.50 | 1 | 2018-07-01 |
2 | 2018-07-01 | 14073 | 刘某某 | 男 | -8.0 | -8.00 | 1 | 2018-07-01 |
3 | 2018-07-01 | 14074 | 周某某 | 男 | -14.3 | -7.15 | 2 | 2018-07-01 |
4 | 2018-07-01 | 14097 | 毛某某 | 男 | -10.0 | -10.00 | 1 | 2018-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-01 | 2018-08-01 | 2018-09-01 | 2018-10-01 | 2018-11-01 | 2018-12-01 | 2019-01-01 | |
---|---|---|---|---|---|---|---|
bf_StudentID | |||||||
13012 | 0.0 | 0.0 | 10.0 | 10.0 | 15.0 | 7.0 | 9.0 |
13564 | 17.0 | 25.0 | 82.0 | 63.0 | 69.0 | 75.0 | 50.0 |
13599 | 8.0 | 10.0 | 39.0 | 33.0 | 34.0 | 31.0 | 26.0 |
13685 | 8.0 | 12.0 | 28.0 | 33.0 | 34.0 | 39.0 | 17.0 |
13947 | 9.0 | 22.0 | 81.0 | 64.0 | 66.0 | 72.0 | 63.0 |
# 用applymap+lambda转换数据,只要当月消费超过30次,记为1,反之为0。
pivoted_purchase = pivoted_counts.applymap(lambda x: 1 if x > 30 else 0)
pivoted_purchase.head()
2018-07-01 | 2018-08-01 | 2018-09-01 | 2018-10-01 | 2018-11-01 | 2018-12-01 | 2019-01-01 | |
---|---|---|---|---|---|---|---|
bf_StudentID | |||||||
13012 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
13564 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |
13599 | 0 | 0 | 1 | 1 | 1 | 1 | 0 |
13685 | 0 | 0 | 0 | 1 | 1 | 1 | 0 |
13947 | 0 | 0 | 1 | 1 | 1 | 1 | 1 |
然后,先 def 定义函数 再apply函数到dataframe。
# 本次学生消费案例:
# 接下来进行用户分层,我们按照学生的消费行为,简单划分成几个维度:新生(新用户)、超爱饭堂的学生(活跃用户)、不爱饭堂的学生(不活跃用户)、偶尔爱饭堂的学生(回流用户)。
# 新生的定义是第一次消费超过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
# 函数写得比较复杂,主要分为两部分的判断,以本月是否“消费30次以上”为界。
# 本月没有“消费30次以上”,还要额外判断他是不是新生(新客),
# 因为部分学生是9月份才“消费30次以上”成为新生(新客),那么在7、8月份他应该连新生(新客)都不是,用unreg表示。
# 如果是老客(即前一个月消费30次以上),则为unlike_canteen。
# 本月若有“月消费30次以上”,需要判断是不是第一次“月消费30次以上”,上一个时间窗口有没有“月消费30次以上”。
# 大家可以多调试几次理顺里面的逻辑关系,对用户进行分层,逻辑确实不会简单,而且这里只是简化版本的。
然后使用apply函数,首次运行报错。如下。
("‘Series’ object is not callable", ‘occurred at index 13012’)
# 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
搜索series和apply函数关系无果后,回到报错提示详情去看,发现了一些线索。(下面的字那么大我也不想的,这编辑器有毒。。。)
看下述报错详情,首先提示错误在(x);
接下来提示错误在active_status(data)的第六行:
----> 6 if data(i) < 30:
缩小到行了,再一细看,果然发现了一个小括号data(i)。
而根据多出查询说明,报错"‘XXX’ object is not callable"一般是因为该用中括号的地方用了小括号。
回到原参考帖去看,果然人家是中括号啊~~~字太小了看不清,认错敲错了。。。
# 这么看来,前面报错(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')
# -------------------------------------------------------
下面,不是"‘Series’ object is not callable"这个问题了,倒是另一个手误输错数字,或者说逻辑一时混乱输错数字,的纠错过程。一并留下,供参考。
其实有个疑问,如果有高手看到还望解答一二,十分感谢。
就是,每次apply函数作用于原dataframe后,会生成一个新的对象(如下表)。但是又不像表,不能进行一些常见的操作。
搜索了一圈大神帖子,首先市面上相关帖子还比较少,然后根据其中一些解决方案依葫芦画瓢,“将对象转成dataframe”,但是转了之后,原对象中括号里的所有内容(含各个逗号和各个apply结果)都混到了一列中,而且没有列名。。。
总而言之就是没成功转成dataframe。。
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
pivoted_purchase_status.columns
out:
**# AttributeError: ‘Series’ object has no attribute ‘columns’
原来是series格式。
那如何转成dataframe呢?**
网上找了一圈,找到了pd.DataFrame这个法子,试试。
pivoted_purchase_status=pd.DataFrame(pivoted_purchase_status)
pivoted_purchase_status.head()
out:
(长这样,只有两列,一列学号,另一列是中括号里面的所有内容)
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…
pivoted_purchase_status.columns
out:RangeIndex(start=0, stop=1, step=1)
稀奇古怪的RangeIndex,是啥。。。能否分列?Excel好办,不过Python…应该更简单吧。。
问题是,没有列名,何来分列。。。
仍然试试,使用索引2来表示第二列[2]。
month=pivoted_purchase_status[2].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: 2
这个问题其实后来解决了一丢丢,把格式搞正确成dataframe了,详见另一篇博客:
https://blog.csdn.net/weixin_44216391/article/details/89329804