Pandas高级数据分析快速入门之三——数据挖掘与统计分析篇

Posted 肖永威

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Pandas高级数据分析快速入门之三——数据挖掘与统计分析篇相关的知识,希望对你有一定的参考价值。

Pandas高级数据分析快速入门之一——Python开发环境篇
Pandas高级数据分析快速入门之二——基础篇
Pandas高级数据分析快速入门之三——数据挖掘与统计分析篇
Pandas高级数据分析快速入门之四——数据可视化篇
Pandas高级数据分析快速入门之五——机器学习特征工程篇
Pandas高级数据分析快速入门之六——机器学习预测分析篇

0. 前言

Pandas高级数据分析的数据挖掘过程与传统的统计分析相似,也就是从数据源提取、扩展特征,形成新的统计表,更高级、科学的方面包括:

  • 大数据全样本
  • 使用机器学习技术增强特征
  • 数据分析方法更丰富,例如相关性、相似性、趋势、聚类等等


本文重点在Pandas数据处理过程和Pandas数据挖掘过程。通过挖掘、统计分析客户加油交易行为,形成客户画像。

1. 原数据挖掘——交易明细

客户给车辆加油时一种持续、周期性较强的交易行为。如下图所示加油消费时序过程。

客户在加油站加油,及非油消费,在数据层面的体现为客户)、交易时间、交易地点(略)、交易物品(油、非油)、油价、加油量、消费金额、充值金额等信息。

上表中数据项定义如下所示:

1.1. 读取原数据

import pandas as pd
import datetime
import numpy as np

cols_name = ['carduser_id','occurday','balance','recharge','volumn','all_oils','all_goods',
                     'discount','goods_id','DIM_DATE','END_DATE','ZDE_92']
df = pd.read_csv('trade.csv',usecols=cols_name,encoding='utf_8_sig')
print(df)
       carduser_id   occurday  balance  recharge  volumn  all_oils  all_goods  discount  goods_id    DIM_DATE   END_DATE  ZDE_92
0          1313943   2019/1/3    42799         0       0         0     -20000         0  100016.0  2018/12/15  2019/2/14   -0.10   
1          1313943   2019/1/9    22799         0    3101    -20000          0         0  102546.0  2018/12/15  2019/2/14   -0.10  
2          1313943  2019/1/19        0         0    3654    -22799          0         0  102546.0  2018/12/15  2019/2/14   -0.10
3          1313943  2019/4/26     4000     40600    5229    -36600          0         0  102546.0   2019/4/13  2019/4/26   -0.05   
4          1313943  2019/6/30        0     22800    3459    -22000      -4800         0  102546.0   2019/6/27   2019/7/9   -0.10   
...            ...        ...      ...       ...     ...       ...        ...        ...       ...         ...        ...     ... 

1.2. 时序数据挖掘

对于数据表中的时间数据(例如:2019/1/3),Pandas能取到年、月、年月、日、时、分、秒、季节、周、节假日、两日间隔时长等等,Pandas举例如下所示。

其中:

  • pandas排序sort_values():tradedf.sort_values(by=[carduser_id,dt_col], ascending=True),按carduser_id,dt_col两个字段联合排序
  • pandas差分diff():tradedf[‘date’].diff(),计算字段‘date’的时间差,再通过tradedf[‘date’].diff().dt.days中的dt.days获取日期差
  • pandas分组groupby():tradedf[[‘carduser_id’,‘date’,‘days’]].groupby([‘carduser_id’], as_index=False)
# 交时序易特征挖掘、提取
def trade_feature(tradedf):
    # 恢复为原有的字段名称(利旧源代码)
    tradedf = tradedf.rename(columns={'all_oils':'amount','all_goods':'goods','goods_id':'goodscategory_id'})
    carduser_id = 'carduser_id'
    dt_col = 'occurtime'
    
    tradedf['occurtime']=tradedf['occurday']
    tradedf['tradecount']=1 #默认一条记录为一次交易,便于统计
    
    tradedf['occurtime'] = tradedf['occurtime'].astype('datetime64')
    tradedf = tradedf.sort_values(by=[carduser_id,dt_col], ascending=True)
    
    tradedf['date'] = tradedf[dt_col].dt.strftime('%Y%m%d')
    tradedf['date'] = tradedf['date'].astype('datetime64') 
    #时间差分,提取本次加油距上次加油间隔天数
    tradedf['days'] = tradedf['date'].diff().dt.days
    #每客户首条记录,间隔时间为0,每个客户首行间隔时间,实际是不知道的,计算没有意义。
    tradedf.loc[tradedf[['carduser_id','date','days']].groupby(['carduser_id'], as_index=False).head(1).index,'days'] = 0
    # data:提取日期型和时间型的特征变量
    tradedf['year']= tradedf[dt_col].dt.year
    tradedf['month'] = tradedf[dt_col].dt.month
    tradedf['day'] = tradedf[dt_col].dt.day
    tradedf['yearmonth'] = tradedf[dt_col].dt.strftime('%Y%m')
    
    tradedf['initdate']=tradedf['date']  #取交易起始时间 
    
    return tradedf

注:pandas时间datetime有丰富的接口“.dt”,dt.year、dt.month、dt.day、dt.hour、dt.minute、dt.second、dt.week 分别返回日期的年、月、日、小时、分、秒及一年中的第几周。

trade_df = trade_feature(df)
print(trade_df)
       carduser_id   occurday  balance  recharge  volumn  amount  goods  discount  goodscategory_id    DIM_DATE  ... ZDE_92  occurtime tradecount       date  days  year  month  day  yearmonth   initdate
0          1313943   2019/1/3    42799         0       0       0 -20000         0          100016.0  2018/12/15  ...  -0.10 2019-01-03          1 2019-01-03   0.0  2019      1    3     201901 2019-01-03
1          1313943   2019/1/9    22799         0    3101  -20000      0         0          102546.0  2018/12/15  ...  -0.10 2019-01-09          1 2019-01-09   6.0  2019      1    9     201901 2019-01-09
2          1313943  2019/1/19        0         0    3654  -22799      0         0          102546.0  2018/12/15  ...  -0.10 2019-01-19          1 2019-01-19  10.0  2019      1   19     201901 2019-01-19
3          1313943  2019/4/26     4000     40600    5229  -36600      0         0          102546.0   2019/4/13  ...  -0.05 2019-04-26          1 2019-04-26  97.0  2019      4   26     201904 2019-04-26
4          1313943  2019/6/30        0     22800    3459  -22000  -4800         0          102546.0   2019/6/27  ...  -0.10 2019-06-30          1 2019-06-30  65.0  2019      6   30     201906 2019-06-30
...            ...        ...      ...       ...     ...     ...    ...       ...               ...         ...  ...    ...        ...        ...        ...   ...   ...    ...  ...        ...        ...

1.3. 数据计算

本文案例中的消费支出以负数表示,不方式统计、观看,把这部分数据转换为正数,以及计算交易合计金额。

def trade_amount(df):
    #交易金额合计
    df['sumamount'] = -(df['amount'] + df['goods']) #负值转换为正
    df['amount'] = -df['amount']  #负值转换为正
    df['goods'] = -df['goods']  #负值转换为正
    
    return df

1.4. 挖掘数据周期维度

时序数据隐含有时间窗口特性、周期性。例如客户常见交易汇总统计包括:按天统计、按月统计、按年统计等。本文增加按月统计客户加油交易数据。

对于周期性,比较明显的是季节性、月份,总体特征比较明显,例如2月份过年前,就是典型的消费高峰期。

# 月统计数据
def monthStatistics(df):
    ms_df = df[['carduser_id','yearmonth','sumamount','recharge','amount','goods','volumn','tradecount'
                                        ]].groupby(['carduser_id','yearmonth'], as_index=False).sum() 
    # 余额月均值
    MonthBalance = df[['carduser_id','yearmonth','balance']].groupby(['carduser_id','yearmonth'], as_index=False).mean() 
    ms_df = pd.merge(left=ms_df,right=MonthBalance,how="left",on=['carduser_id','yearmonth'])
    del MonthBalance
        
    ms_df['month'] = ms_df['yearmonth'].str[4:6]
    ms_df['year'] = ms_df['yearmonth'].str[0:4]
    ms_df['yearmonth'] = ms_df['yearmonth'].astype('int64')   
    ms_df['month'] = ms_df['month'].astype('int64')      
    ms_df['year'] = ms_df['year'].astype('int64')

    ms_df = ms_df.sort_values(by=['carduser_id','yearmonth'], ascending=True)
    print(ms_df)
    
    return ms_df
ms_df = monthStatistics(trade_df)
      carduser_id  yearmonth  sumamount  recharge  amount  goods  volumn  tradecount       balance  month  year
0         1313943     201901      62799         0   42799  20000    6755           3  21866.000000      1  2019 
1         1313943     201904      36600     40600   36600      0    5229           1   4000.000000      4  2019 
2         1313943     201906      26800     22800   22000   4800    3459           1      0.000000      6  2019 
3         1313943     201907      22000     22000   22000      0    3390           1      0.000000      7  2019 
4         1313943     201909      72600     72600   62000  10600    9477           3   3533.333333      9  2019 
...           ...        ...        ...       ...     ...    ...     ...         ...           ...    ...   ... 

1.5. 表关联(merge)

pandas的merge方法提供了一种类似于SQL的表关联操作,官网文档提到它的性能会比其他开源语言的数据操作要高效。

merge的参数

  • left:左侧的DataFrame
  • right:右侧的DataFrame 或 named Series
  • on:关联列名,用到这个参数的时候一定要保证左表和右表用来对齐的那一列都有相同的列名。
  • left_on:左表对齐的列,可以是列名,也可以是和dataframe同样长度的arrays。
  • right_on:右表对齐的列,可以是列名,也可以是和dataframe同样长度的arrays。
  • left_index/ right_index: 如果是True的haunted以index作为对齐的key
  • how:数据融合的方法,‘left’, ‘right’, ‘outer’, ‘inner’, ‘cross’, 默认是 ‘inner’。
  • sort:根据dataframe合并的keys按字典顺序排序,默认是,如果置false可以提高表现。
def mergergoods(trade_df):
    # 提取物料编码特征
    goodsCode = pd.read_csv('goodscode.csv',encoding='utf_8_sig')
    goodsCode = goodsCode.rename(columns={'id':'goodscategory_id'})
    trade_df = pd.merge(left=trade_df, right=goodsCode,how="left",on='goodscategory_id')
    print(trade_df)
    
    return trade_df
trade_df = mergergoods(trade_df)
       carduser_id   occurday  balance  recharge  volumn  amount  goods  discount  goodscategory_id    DIM_DATE  ...  days  year month  day yearmonth   initdate  sumamount        catname        h1        h2  
0          1313943   2019/1/3    42799         0       0       0  20000         0          100016.0  2018/12/15  ...   0.0  2019     1    3    201901 2019-01-03      20000             香烟  100015.0  100016.0   
1          1313943   2019/1/9    22799         0    3101   20000      0         0          102546.0  2018/12/15  ...   6.0  2019     1    9    201901 2019-01-09      20000   92号 车用汽油(ⅥA)  100000.0  100002.0   
2          1313943  2019/1/19        0         0    3654   22799      0         0          102546.0  2018/12/15  ...  10.0  2019     1   19    201901 2019-01-19      22799   92号 车用汽油(ⅥA)  100000.0  100002.0   
3          1313943  2019/4/26     4000     40600    5229   36600      0         0          102546.0   2019/4/13  ...  97.0  2019     4   26    201904 2019-04-26      36600   92号 车用汽油(ⅥA)  100000.0  100002.0   
4          1313943  2019/6/30        0     22800    3459   22000   4800         0          102546.0   2019/6/27  ...  65.0  2019     6   30    201906 2019-06-30      26800   92号 车用汽油(ⅥA)  100000.0  100002.0   
...            ...        ...      ...       ...     ...     ...    ...       ...               ...         ...  ...   ...   ...   ...  ...       ...        ...        ...            ...       ...       ...   

1.6. 小结

至此,本文基于数据源,输出了两个交易数据集:一是扩展时间特征的日交易数据集,二是按月统计归集的月交易数据集。

2. 常用特征提取——极限值与统计值

数据挖掘与分析的目标是为关注对象(客户)打上特征数据,例如当前余额、最后一次交易信息、客户时长、累计交易金额、平均间隔时长等等。

2.1. 最后一次交易关键特征

新建客户特征数据集,首先从交易数据集中获取每个客户的最后一次交易关键信息,为每个客户建立唯一特征数据记录。

按每客户升序排序,取最后一条记录’tail(1)'提取数据。接着合并统计数据,例如累计交易次数、平均间隔天数。

def customer_last(trade_df):    
    # 取最后一次交易间隔时间 
    cf_df = trade_df[['carduser_id','occurday','balance','days']].groupby(['carduser_id']).tail(1)
    cf_df['occurtime'] = cf_df['occurday'].astype('datetime64') 
    cf_df = cf_df.rename(columns={'occurday':'latestday','days':'latestdays'}) 
    
    return cf_df
cf_df = customer_last(trade_df)
     carduser_id   latestday  balance  latestdays  occurtime  
0        1313943  2020-12-07       60         1.0 2020-12-07      
1        1505320  2020-12-30   105072        17.0 2020-12-30 
2        1726223  2021-01-31    41910         7.0 2021-01-31
3        1726514  2021-01-14       10        19.0 2021-01-14
4        1728962  2021-01-10    85999        19.0 2021-01-10 
..           ...         ...      ...         ...        ...       

2.2. 关键极值特征

  • 最后充值时间:
    筛选充值记录,按时间升序排序,取最大值‘tail(1)’,再与最后的交易记录做差,计算与最后交易的间隔时间。

  • 最大间隔时间:
    按间隔时间升序排序,取最大值‘tail(1)’。

def customer_extreme(cf_df ,trade_df):
    # 取最后一次充值时间,由于date已经变成int64,需要使用initdate列
    recharge = trade_df[trade_df['recharge']>0][['carduser_id','initdate','recharge']].groupby(['carduser_id']).tail(1)
    lasttrade = trade_df[['carduser_id','initdate']].groupby(['carduser_id']).tail(1).copy()
    recharge = recharge.rename(columns={'initdate':'redate','recharge':'lastrecharge'})
    lasttrade = pd.merge(left=lasttrade,right=recharge,how='inner',on=['carduser_id'])
     
    lasttrade['recharge_days'] = (lasttrade['initdate'].astype('datetime64')-lasttrade['redate'].astype('datetime64')).dt.days
    cf_df = pd.merge(left=cf_df,right=lasttrade[['carduser_id','recharge_days','lastrecharge']],how='left',on=['carduser_id'])   
    #取最大间隔时间
    maxtrade = trade_df.sort_values(by=['carduser_id','days'], ascending=True)
    maxtrade = maxtrade[['carduser_id','days']].groupby(['carduser_id']).tail(1)
    cf_df = pd.merge(left=cf_df, right=maxtrade,how="left",on='carduser_id') 
    cf_df['daysnum'] = (cf_df['occurtime'] -cf_df['initdate'].astype('datetime64')).dt.days #客户会员时长,天数       
    cf_df = cf_df.fillna(0) # 空值填充为0   
    
    return cf_df
cf_df = customer_extreme(cf_df ,trade_df)
print(cf_df)
     carduser_id   latestday  balance  latestdays  occurtime  recharge_days  lastrecharge   initdate   days  daysnum 
0        1313943   2021/6/27     1065        18.0 2021-06-27            0.0       22000.0 2019-01-03  237.0      906  
1        1456894   2019/7/26        0       145.0 2019-07-26            0.0           0.0 2019-03-03  145.0      145  
2        1459877   2020/8/18        0        21.0 2020-08-18            0.0           0.0 2020-07-28   21.0       21  
3        1480968   2019/4/25    24870         0.0 2019-04-25            0.0       50000.0 2019-04-25    0.0        0  
4        1481117  2020/11/20        0         0.0 2020-11-20            0.0           0.0 2020-11-20    0.0        0  
..           ...         ...      ...         ...        ...            ...           ...        ...    ...      ... 

2.3. 累计/均值/计数等通常特征

使用DataFrame的聚合方法sum、count、mean等,统计累计交易次数、间隔时间均值、充值次数、累计消费情况等。

#输入特征集、日交易集
def customer_aggregation(cf_df,trade_df):
    # 取累计交易次数
    tradecount_df = trade_df[['carduser_id','tradecount']].groupby(['carduser_id'],as_index=False).sum()        
    cf_df = pd.merge(left=cf_df,right=tradecount_df,how='left',on='carduser_id')   
    
    # 取间隔均值
    meandaystrade_df =trade_df[trade_df['days']>0].reset_index(drop=True) 
    meandays_df = meandaystrade_df[['carduser_id','days','balance']].groupby(['carduser_id'],as_index=False).mean().round(2)
    meandays_df = meandays_df.rename(columns={'days':'meandays','balance':'balancemean'})       
    cf_df = pd.merge(left=cf_df,right=meandays_df,how='left',on='carduser_id')  
    
    rechrage_count_df = trade_df.loc[trade_df['recharge']>0].reset_index(drop=True).copy()
    rechrage_count_df = rechrage_count_df[['carduser_id','recharge']].groupby(['carduser_id'],as_index=False).count()
    rechrage_count_df = rechrage_count_df.rename(columns={'recharge':'count'})
    cf_df = pd.merge(left=cf_df, right=rechrage_count_df,how="left",on='carduser_id')       
    
    acc_trade = trade_df[['carduser_id','sumamount','amount','goods','volumn']].groupby(['carduser_id'], as_index=False).sum()
    acc_trade = acc_trade.rename(columns={'sumamount':'accsumamount','amount':'accamount','goods':'accgoods','volumn':'accvolumn'})
    cf_df = pd.merge(left=cf_df, right=acc_trade,how="left",on='carduser_id')
    
    return cf_df
cf_df = customer_aggregation(cf_df,trade_df)
print(cf_df)
     carduser_id   latestday  balance  latestdays  occurtime  recharge_days   lastrecharge   initdate   days  daysnum  tradecount  meandays  balancemean  count  accsumamount  accamount  accgoods  accvolumn
0        1313943   2021/6/27     1065        18.0 2021-06-27            0.0        22000.0 2019-01-03  237.0      906          46     20.13      5852.51   29.0        672034     616514     55520     101337
1        1456894   2019/7/26        0       145.0 2019-07-26            0.0            0.0 2019-03-03  145.0      145           2    145.00         0.00    NaN         45000      45000         0       6625
2        1459877   2020/8/18        0        21.0 2020-08-18            0.0            0.0 2020-07-28   21.0       21           2     21.00         0.00    NaN         50000      50000         0       8788
3        1480968   2019/4/25    24870         0.0 2019-04-25            0.0        50000.0 2019-04-25    0.0        0           1       NaN          NaN    1.0         33000      33000         0       4714
4        1481117  2020/11/20        0         0.0 2020-11-20            0.0            0.0 2020-11-20    0.0        0           1       NaN          NaN    NaN          9400       9400         0       1691
..           ...         ...      ...         ...        ...            ...            ...        ...    ...      ...         ...       ...          ...    ...           ...        ...       ...        ...

2.4. 方差/标准差等波动特征

方差、标准差、峰度、偏度做为客户交易特征,可以用于客户细分的特征。

# 波动分析,方差、偏度、峰度
def fluctuation_analysis(cf_df ,df):
    #计算加油消费金额、加油量、加油间隔的标准差
    df_feature = df[['carduser_id','volumn','amount','goods','sumamount','days']].groupby(['carduser_id']).std()
    df_feature = df_feature.rename(columns={'amount':'amount_std','volumn':'volumn_std','goods':'goods_std','sumamount':'sumamount_std','days':'days_std'})
    cf_df = pd.merge(left=cf_df, right=df_feature,how="left",on=['carduser_id'])   #左连接 

    # 偏度(三阶)
    df_feature4 = df[['carduser_id','volumn','amount','goods','sumamount','days']].groupby(['carduser_id']).skew()
    df_feature4 = df_feature4.rename(columns={'amount'Pandas高级数据分析快速入门之五——机器学习特征工程篇

Pandas高级数据分析快速入门之五——机器学习特征工程篇

Pandas高级数据分析快速入门之一——Python开发环境篇

Pandas高级数据分析快速入门之六——机器学习预测分析篇

Pandas高级数据分析快速入门之二——基础篇

Pandas高级数据分析快速入门——全过程综述及案例集锦