数据处理与统计分析篇-day09-数据透视表与日期时间处理
一. 数据透视表
概述
-
数据透视表(Pivot Table)是一种交互式的表,可以进行某些计算,如求和与计数等。
-
所进行的计算与数据跟数据透视表中的排列有关。之所以称为数据透视表,是因为可以动态地改变它们的版面布置,以便按照不同方式分析数据,也可以重新安排行号、列标和页字段。每一次改变版面布置时,数据透视表会立即按照新的布置重新计算数据。
-
另外,如果原始数据发生更改,则可以更新数据透视表。在使用Excel做数据分析时,透视表是很常用的功能,Pandas也提供了透视表功能,对应的API为pivot_table
pivot_table函数介绍
两个pivot_table函数
-
pandas.pivot_table
-
pandas.DataFrame.pivot_table
pandas.pivot_table 比 pandas.DataFrame.pivot_table 多了一个参数data,data就是一个dataframe,实际上这两个函数相同
pivot_table中参数
-
values: 要做聚合操作的列名
-
index: 行索引, 传入原始数据的列名, 这一列中每个取值会作为透视表结果的1个行索引.等价于: groupby()的分组字段.
-
columns: 列索引, 传入原始数据的列名, 这一列中每个取值会作为透视表结果的1列.
-
aggfunc: 聚合函数(默认为mean)
解释:
参1: index, 索引列, 等价于: groupby()的分组字段.参2: columns, 列索引, 要写的也是 原表中的 列名. 参3: values, 统计字段, 等价于: groupby()的聚合字段.参4: aggfunc, 聚合函数, 默认为: mean
会员分析
分析会员运营的基本情况, 从量的角度分析会员运营情况:
① 整体会员运营情况(存量,增量)
② 不同渠道(线上,线下)的会员运营情况
③ 线下业务,拆解到不同的地区、门店会员运营情况
从质的角度分析会员运营情况:
① 会销比, 即: 会员消费占整体消费的占比
② 连带率, 即: 是不是每次购买商品的时候, 都购买一件以上
③ 复购率, 即: 是不是买了之后, 又来买
需求1: 月增量
# 思路1: groupby() + 聚合函数实现. # 1. 给原始的df对象, 新增1列, 表示 注册年月. # customer_info['注册年月'] = customer_info['注册时间'].apply(lambda x : x.strftime('%Y-%m')) # %Y: 年, %m: 月 customer_info.loc[:, '注册年月'] = customer_info['注册时间'].apply(lambda x : x.strftime('%Y-%m')) # %Y: 年, %m: 月 customer_info #%% # 2. 从原始的df对象中, 获取我们要的字段查看即可. customer_info[['会员卡号', '会员等级', '会员来源', '注册时间', '注册年月']].head(10) #%% # 3. 计算 月增量 = 每个月新增的会员数. month_count = customer_info.groupby('注册年月')[['会员卡号']].count() month_count.columns = ['月增量'] month_count customer_info.注册年月.value_counts().sort_index() # 效果同上. #%% # 思路2: pivot_table() 透视表的方式实现. # 参1: index, 索引列, 等价于: groupby()的分组字段. # 参2: columns, 列索引, 要写的也是 原表中的 列名. # 参3: values, 统计字段, 等价于: groupby()的聚合字段. # 参4: aggfunc, 聚合函数, 默认为: mean customer_info.pivot_table(index='注册年月', values='会员卡号', aggfunc='count')
需求2: 月存量
# 1. 计算月存量 month_count['存量'] = month_count['月增量'].cumsum() # 2. 打印结果. month_count # 3. 可视化数据. # 3.1 绘制 月增量, figsize=宽高, color=颜色, secondary_y: 启用双Y轴, legend: 图例, grid: 网格, xlabel=X轴标签, ylabel=Y轴标签 month_count.月增量.plot(figsize=(20, 10), color='green', secondary_y=True, legend=True) # 3.2 绘制 月存量 month_count.存量.plot(kind='bar', figsize=(20, 10), color='pink', xlabel='年月', ylabel='会员数量', legend=True, grid=True) # 3.3 绘制 标题. plt.title('月增量/存量结果分析', fontsize=21)
需求3: 增量等级分布
# 思路1: 分组 + 聚合函数 # unstack()重构索引, 将会员等级作为列名, 即: 把数据结果转换为矩阵 customer_info.groupby(['注册年月', '会员等级'])['会员卡号'].count().unstack() # 思路2: 透视表 member_rating = customer_info.pivot_table(index='注册年月', columns='会员等级', values='会员卡号', aggfunc='count') member_rating # 可视化 # 1. 构建画布, 坐标系 fig, ax1 = plt.subplots(figsize=(20, 10)) # 基于ax1坐标对象, 构建ax2 ax2 = ax1.twinx() # 绘制会员等级 member_rating[1:][['白银会员', '黄金会员']].plot(kind='bar', ax=ax1, legend=True, xlabel='年月', ylabel='白银/黄金', grid=True) member_rating[1:][['铂金会员', '钻石会员']].plot(ax=ax2, legend=True, ylabel='铂金/钻石', grid=True, color=['red', 'green']) ax2.legend(loc='upper left') plt.title('增量等级分布', fontsize=21) plt.show()
需求4: 增量会员占比
# 计算会员总数 member_rating['总计'] = member_rating.sum(axis=1) member_rating #%% # 2. 计算每月增量会员等级占比, 黄金, 白银 # member_rating['白银会员占比'] = member_rating['白银会员'] / member_rating['总计'] # member_rating['黄金会员占比'] = member_rating['黄金会员'] / member_rating['总计'] member_rating['白银会员占比'] = member_rating['白银会员'].div(member_rating['总计']) member_rating['黄金会员占比'] = member_rating['黄金会员'].div(member_rating['总计']) member_rating # 效果同上 #%% # 绘图 # grid: 网格, legend: 图例 member_rating[1:][['白银会员占比', '黄金会员占比']].plot(figsize=(20, 10), grid=True, legend=True, xlabel='年月', ylabel='占比', color=['r', 'g', 'b']) plt.title('增量会员-等级占比', fontsize=21) plt.show() #%% # 分组会员等级 member = customer_info.pivot_table(index='注册年月', values='会员卡号', columns='会员等级', aggfunc='count') member #%% member['总计'] = member.sum(1) # 计算每行会员数据 member['白银会员占比'] = member['白银会员'].div(member['总计']) member['黄金会员占比'] = member['黄金会员'].div(member['总计']) member
需求5: 整体等级分布
# 计算会员数量 # member_cnt = customer_info.groupby('会员等级')[['会员卡号']].count() member_cnt = customer_info.pivot_table(index='会员等级', values='会员卡号', aggfunc='count') # 修改列名 member_cnt.columns = ['会员数'] # 新增占比列 member_cnt['占比'] = member_cnt.会员数.div(member_cnt.会员数.sum()) # 筛选指定列 member_cnt.loc[['白银会员','铂金会员','黄金会员','钻石会员'], '占比'].plot.pie(figsize=(16,8), autopct='%.2f%%',fontsize=16, legend=True) plt.show()
需求6: 线上下增量分析
# internet = customer_info.groupby(['注册年月', '会员来源'])[['会员卡号']].count() internet = customer_info.pivot_table(index='注册年月', values='会员卡号', aggfunc='count', columns='会员来源') internet.plot() plt.show()
二. 日期时间处理
from datetime import datetime import matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['SimHei'] # 正常显示汉字 plt.rcParams['axes.unicode_minus'] = False # 正常显示负号 import pandas as pd import os os.chdir(r'D:\CodeProject\03data_processing_analysis\my_project') os.getcwd()
python中的时间类型
概述
-
和其它语言类似, Python内置了datetime对象,可以在datetime库中找到
-
pandas的日期时间类型默认是 datetime64[ns]
Python-日期时间类型
# 获取当前时间 d1 = datetime.now() d1 # datetime.datetime(2024, 9, 23, 10, 41, 49, 615496), 精确到毫秒 #%% # 手动指定日期 d2 = datetime(2024, 10, 1) d2 # datetime.datetime(2024, 10, 1, 0, 0) #%% # 日期差 d2 - d1 # datetime.timedelta(days=6, seconds=48580, microseconds=704938)
Pandas-日期时间类型
# 1. 手动转换 d1 = pd.to_datetime('2024-10-01') d1 # Timestamp('2024-10-01 00:00:00'), 基于python的datetime类型的升级, 功能更强大, 能结合时区, 时差等运算, 且可以精确到 纳秒 #%% # 演示把python的datetime类型 => pandas的datetime64类型 # 方式1: 导入后转换 ebola = pd.read_csv('data/country_timeseries.csv') ebola.info() #%% # 把 ebola的 Date列 => 日期类型. # 方式1: pd.to_datetime()函数实现. ebola['Date_df'] = pd.to_datetime(ebola['Date']) ebola.info() #%% # 方式2: 读取数据时转换 # 参数parse_dates解释: 在加载数据是, 设置某列字段为 日期类型, 可以是传入: 列名, 也可以传入: 列的编号 ebola = pd.read_csv('data/country_timeseries.csv', parse_dates=[0]) ebola.info() #%% # Timestamp直接获取 日期的类型 d2 = pd.Timestamp('2024-10-01') d2 # Timestamp('2024-10-01 00:00:00')
提取日期的各个部分
从python的datetime类型中, 提取各部分
# 1. 从python的datetime类型中, 提取各部分 d1 = datetime(2024, 10, 1) d1 # datetime.datetime(2024, 10, 1, 0, 0) d1.year d1.month d1.day d1.dayofyear # 没有这个属性
从pandas的datetime64类型中, 提取各部分
# 2. 从pandas的datetime64类型中, 提取各部分 d2 = pd.to_datetime('2024-10-01') d2 # Timestamp('2024-10-01 00:00:00') d2.year d2.month d2.day d2.dayofyear # 275 #%% # 3. 从埃博拉数据中, 提取各部分 # 把Series对象 => DatetimeProperties 日期属性对象 ebola['Date'].dt # <pandas.core.indexes.accessors.DatetimeProperties object at 0x000002B40154EDB0> ebola['year'] = ebola['Date'].dt.year ebola['month'] = ebola['Date'].dt.month ebola['day'] = ebola['Date'].dt.day ebola['dayofyear'] = ebola['Date'].dt.dayofyear ebola[['year', 'month', 'day', 'dayofyear']]
日期案例-银行数据
# 加载数据 bank = pd.read_csv('data/banklist.csv', parse_dates=[5, 6]) bank.info() #%% # 增加两列表示倒闭年和倒闭季度 bank['Closing year'], bank['Closing quarter'] = (bank['Closing Date'].dt.year, bank['Closing Date'].dt.quarter) bank
计算每年倒闭数量
# 计算每年倒闭数量 bank.groupby(['Closing year'])['Bank Name'].count() bank.pivot_table(index='Closing year', values='Bank Name', aggfunc='count')
计算每年每季度
# 计算每年每季度 bank.groupby(['Closing year', 'Closing quarter'])['Bank Name'].count() bank.pivot_table(index=['Closing year', 'Closing quarter'], values='Bank Name', aggfunc='count')
可视化
# 可视化 bank.pivot_table(index=['Closing year', 'Closing quarter'], values='Bank Name', aggfunc='count').plot(figsize=(10, 5), grid=True) plt.show()
生成时间序列
格式
pd.date_range('起始时间','结束时间', freq= 生成时间序列的方式) # frequency:出现频次
参数
代码演示
# 格式: pd.date_range(开始时间, 结束时间, freq='生成规则') # 包左包右, 默认生成方式是: D 即: 日历日期 pd.date_range('2024-09-01', '2024-10-31') # 获取工作日 pd.date_range('2024-09-01', '2024-10-31', freq='B') # 间隔一个工作日获取 pd.date_range('2024-09-01', '2024-10-31', freq='2B') # 获取每月第一个周四 pd.date_range('2024-09-01', '2024-10-31', freq='WOM-1THU') # 获取每月第三个周五 pd.date_range('2024-09-01', '2024-10-31', freq='WOM-3FRI')
日期运算
案例1-埃博拉数据
# 1. 计算各个国家, 疫情爆发的天数 # 埃博拉数据中, Day表示各个国家, 疫情爆发的时间(第几天) ebola['Date'] - ebola['Date'].min() # 2. 给ebola这个df对象新增一列, 表示: 疫情天数, 即: 日期 - 爆发的哪一天 ebola['outbreak_d'] = ebola['Date'] - ebola['Date'].min() # 3. 查看数据 ebola.head() ebola.info() # 能看到 outbreak_d 列为: timedelta64[ns] (日期差类型)
案例2-银行数据(索引)
# 加载数据, 并转换日期类型 tesla = pd.read_csv('data/TSLA.csv', parse_dates=[0]) tesla.info()
获取开盘价最大和最小值
tesla.Open.min() # 142.32000732421875 tesla.Open.max() # 1011.8499755859376
获取2015年8月的数据
# 方式1: df对象[(条件) & (条件)] # & 支持 Series 和 Series的比较 # and 不支持 Series 和 Series的比较 tesla[(tesla.Date.dt.year == 2015) & (tesla.Date.dt.month == 8)]
# 方式2: 使用日期索引(设置索引列) tesla.set_index('Date', inplace=True) tesla.loc['2015-08']
# 方式3: timedelta 类型-时间差类型 # 重置索引 tesla.reset_index(inplace=True) # 日期减去最小日期 tesla['ref_date'] = tesla['Date'] - tesla['Date'].min() # 将timedelta64[ns]设置为索引列 # tesla.set_index('ref_date', inplace=True) tesla.index = tesla.ref_date # 效果同上 # 索引获取指定数据 tesla['0 days' : '12 days']
日期时间类型-总结
Pandas日期时间类型
Pandas关于日期时间的数据 有如下几种数据类型
-
TimeStamp 时间戳 就是一个时间点
-
Datetime64 一列时间数据 →DatetimeIndex
-
TimeDelta64 两列时间的差值 → TimedeltaIndex
日期时间类型转换
如果数据中包含了日期时间的数据, 并且后续计算/数据的处理需要用到日期时间类型数据的特性需要把他转换成日期时间类型
-
pd.to_datetime(一列数据)
-
pd.read_csv(parse_dates= [列名/序号]) 加载的时候直接进行转换
生成日期时间的序列
pd.date_range('起始时间','结束时间', freq= 生成时间序列的方式)
提取日期部分
在特征处理/数据处理 看见日期时间类型数据需要马上反映出通过这一列数据,可以做出很多列特征来
-
df['Date'].dt.year 年
-
df['Date'].dt.month 月
-
df['Date'].dt.quarter 季度
-
df['Date'].dt.dayofweek 星期几
-
如果想快速的对日期进行切片/范围选取的操作, 可以把它转换成日期时间索引