pandas之重塑透视交叉
重塑和轴向旋转
- 有许多用于重新排列表格型数据的基础运算,这些函数也成为重塑或轴向旋转运算
- 数据重塑和轴向选择操作:表示转换一个表格或向量的结构,使其适合于进一步的分析
重塑层次化索引
层次化索引喂DataFrame数据重新排列任务提供一种具有良好的一致性的方式 主要功能有2:
- stack()列转换行,将数据的列索引旋转为行索引;
- 少用,一帮用于将DataFrame转为层次化Series
- unstack() 行转列,将数据的行索引旋转为列索引
- 常用。一般用于将层次化的Series转为DataFrame
- 0.最外层的行索引,unstack(1)指定第二层;或者指定索引名字
# 一个行列索引都带name的DataFrame的对象
data = pd.DataFrame(
np.arange(6).reshape((2,3)),
index=pd.Index(['Ohio','Colorado'],name='state'),
columns=pd.Index(['one','two','three'],name='number')
)
result = data.stack()
# 列转行,与stack互为逆运算
result.unstack()
# 转置操作,行列索引交换
data.unstack().unstack(1)
data.T
- unstack如果不是所有级别值都在各个分组中找到的话,unstack操作会引入缺失的数据
- stack默认会过滤掉缺失数据,该运算可逆
s1 = pd.Series([0,1,2,3],index=['a','b','c','d']) s2 = pd.Series([4,5,6],index=['c','d','e']) data2 = pd.concat([s1,s2],keys=['one','two']) data2.unstack() #数据不会遗失,会有缺失值
轴向旋转的练习
对dataFrame进行unstack操作时,作为旋转轴的级别将会成为结果中的最低级别
df = pd.DataFrame({'left':result,'right':result+5},columns=pd.Index(['left','right'],name='side'))
df.unstack() # 最里面的行索引,转换为列索引仍然是最里面
透视表交叉表
实现数据分析指标计算的常用操作 交叉表--->透视表--->分组聚合--->自定义函数
- 交叉表就是聚合函数是len个数的透视表
- 透视表是由聚合函数是mean的分组旋转而成
- 分组聚合就是自定义函数的一种特定操作
越往底层书写越难,应用范围越广,越往上层书写越简单,应用范围越窄
透视表(pivot table)是各种电子表格程序数据分析中一种高级数据汇总表格形式 数据源表通常只包含行和列,经常有重复的无用值出现在各列下,因为导致源表不能传递有价值的信息,这时候可用透视方法调整源表的布局作用更清晰的展示
透视表是用来汇总其他表的数据
- 首先把源表分组,将不同值当作row,column,value
- 然后对各组内数据做汇总操作,排序,平均,累加,计数等
这种动态将源表转换得到想要的终表的旋转过程,叫做透视
会不会透视表是衡量一个人能否做数据分析项目的基准(会的标准就是会操作透视表和函数计数)
- 入门,用pandas原生的pivot_table方法生成透视表
- 进阶,使用groupby和unstack配合手动构建透视表
- 乞丐版交叉表(一列数据的频度情况,只有一个维度,分组聚合实现) t2.groupby('day').size()
- 常用的crosstab交叉表函数结果(2列数据的频度情况) pd.crosstab(tips.time,[tips.smoker,tips.day],margins=True)
- 常用pivot_table透视表函数结构 常见参数:需要计算的列,行索引,列索引,分项小计默认False,自定义函数默认是mean,缺失值填充 tips.pivot_table(['tip_pct','size'],index=['time','day'],columns='smoker',margins=True,aggfunc=len,fill_value=0) 交叉表只要把aggfunc的参数改为len久可以
- 底层使用分组聚合和轴向旋转实现透视表 分组,行索引,列索引;均值聚合;行索引转列索引;填充缺失值0 tips.groupby(['time','day','smoker'])['size','tip_pct'].mean().unstack().fillna(0) 交叉表只要把哦聚合函数有mean改为size就行
pivot_table其他参数
- 传入margins=True添加分项小计
- 这会将添加的标签为A11的行和列,其值对于单个等级中所有的数据的分组统计
- A11值为平均数,不单独考虑烟民和非烟民(a11列),不单独考虑行分组2哥级别种任何单项(A11行)
- 透视表默认聚合函数是mean()
- 如果使用非默认的聚合函数,传给aggfunc即可(传入函数名称和函数字符串)
- 如使用count或len可得到有关分组大小的交叉表(计数或者频率)
- 传入值类型。一般为函数名字符串,函数名,len count np.max(推荐)
# 例子每周各个天(day)的午餐晚餐(time)小费平均值(pivot_table的默认聚合类型)
# 聚合运算列,行索引,列索引,缺失值填充,,默认是平均值的计算
t2.pivot_table('tip',index='day',columns='time',fill_value=0)
t2.pivot_table('tip',index='day',columns='time',fill_value=0,margins=True) #分项小计的平均值
#* 传入margins=True添加分项小计
#* 这会将添加的标签为A11的行和列,其值对于单个等级中所有的数据的分组统计
#* A11值为平均数,不单独考虑烟民和非烟民(a11列),不单独考虑行分组2哥级别种任何单项(A11行)
tips[tips['time'] == 'dinner']['tip'].mean()
tips.groupby('time')['tip'].mean()
#分组聚合重塑 ,day,time在groupbu分组中作为行索引
t2.groupby(['day','time'])['tip'].mean().unstack().fillna(0)
# 根据day和smoker计算分组平均数,并将day和smoker放在行索引上 # 透视表 tips.pivot_table(index=['day','smoker']) # 分组聚合 tips.groupby(['day','smoker']).mean().sort_index(axis=1) # 如果只想聚合tip_pct和size,而且想根据time进行分组,再将smoker放到列索引上,在把day放在行缩影上 tips.pivot_table(['tip_pct','size'],index=['time','day'],columns='smoker') tips.groupby(['time','smoker','day'])[['tip_pct','size']].mean().unstack(1) # 分组就是行索引,聚合就是列索引 f = tips.pivot_table(['tip_pct','size'],index=['time','day'],columns='smoker',margins=True,aggfunc=len,fill_value=0) #缺失值实以浮点数存储 f.astype(np.int) #确保没有非数字类型 ,否则报错
交叉表
data = pd.DataFrame({
'sample':np.arange(1,11),
'Nationality':['USA','JP','USA','JP','JP','JP','USA','USA','JP','USA'],
'Handedness':['Right-handed','Left-handed','Right-handed','Right-handed','Left-handed','Left-handed','Left-handed','Right-handed','Right-handed','Right-handed']
})
data
jiapd.crosstab(data.Nationality,data.Handedness,margins=True) # 行列。交叉表主要统计个数,统计得个数根据条目数进行
# 使用透视表实现交叉表
data.pivot_table('sample',index='Nationality',columns='Handedness',aggfunc='size')
# 分组聚合,轴旋转实现交叉表效果
y = data.groupby(['Nationality','Handedness']).size().unstack() # groupbyu锁定行索引,剩余得列索引填充列索引数据
# 增加分项小计,axis=1这列得每一行
y.loc['all'] = y.sum()
y['all'] = y.sum(axis=1)
y.astype(np.int)
# 小费数据交叉表;;;统计顾客在每种用餐时间,每个星期下的吸烟数量情况 # 三个索引: tinme,day,smoker pd.crosstab([tips.day,tips.time],tips.smoker) tips.groupby(['day','time','smoker']).size().unstack() tips.pivot_table(index=['time','day'],columns='smoker',aggfunc=len)['day2'] tips.pivot_table(index=['time','day'],columns='smoker',aggfunc=len)['size'] # ['size']就是抽取数据中一个列 tips.pivot_table(index=['day','time'],columns='smoker',aggfunc='size')
在学习到pandas数据规整多层次化转换的时候
frame
# 用分组实现 # 默认传入的是这个列的索引的所有值,而不是green和red,所以推荐使用level='color' frame.groupby(['Green', 'Red', 'Green'], axis=1).sum() frame.groupby(axis=1, level='color').sum()
总结
## 交叉表
# 统计个数,参数行索引,列索引
pd.crosstab(data.Nationality,data.Handedness,margins=True)
#有透视表转换的交叉表,也可以仅仅指定行索引和列索引,因为计算的是所有条目数据的数量
data.pivot_table(index='Nationality',columns='Handedness',aggfunc='size')
# 交叉表统计个数,分组是行索引,所有只需要分组就可以
tips.groupby(['day','time','smoker']).size().unstack().fillna(0).astype(np.int)
# 透视表
# 第一个参数是聚合统计列,tip是列的数据,因为有列索引所以被分为2列,tip相当于groupby的聚合
t2.pivot_table('tip',index='day',columns='time',fill_value=0)
# 分组后就是行索引,聚合后就是列索引,数据自然是列索引
tips.groupby(['day','time','smoker'])[['tip_pct','size']].mean().unstack().fillna(0)
分组对象常见属性和方法
- ngroups: 组的个数 (int)
- size(): 每组元素的个数 (Series)
- groups: 每组元素在原 DataFrame 中的索引信息 (dict)
- get_group(label): 标签 label 对应的数据 (DataFrame)
df = pd.DataFrame({
'name': ['张三','李四','王五','李四','王五','王五','赵六'],
'chinese': [18, 53, 67, 63, 39, 70, 94],
'math': [82, 63, 41, 59, 46, 39, 58],
'english': [68, 52, 90, 86, 60, 98, 64],
'test': ['一','一','一','二','二','三','一']
})
x4 = df.groupby('name')
x4
通过查看分组对象的内部结构,了解其原理
x4.ngroups
x4.size()
x4.groups
x4.get_group('李四')
分组不能直接输出,通过转为列表、字典或遍历查看分组内部结构
#分组转为列表或字典
x5 = list(x4)
x6 = dict(list(x4))
遍历分组内部结构
# 单列分组基准遍历
for method, group in x4:
# print(method) # 分组基准
# print(group) # 分组后的DataFrame
# print(type(group))
x7 = group
x7
# 多列分组遍历内部结构
for (k1, k2),group in df.groupby(['name', 'test']):
# print(k1) # 分组基准1
# print(k2) # 分组基准2
# print(group) # 分组后的DataFrame
x8 = group
x8
自定义聚合方式
在分组聚合的split-apply-combine过程中,apply是核心。Python 本身有高阶函数 apply() 来实现它
自定义聚合方式:aggregate(),或agg()
之前的聚合方式,所有列只能应用一个相同的聚合函数
agg()自定义聚合方式的优势:
聚合参数是列表
对数据每列应用多个相同的聚合函数
聚合参数是字典
对数据的每列应用一个或多个不同的聚合函数
聚合参数是自定义函数
对数据进行一些复杂的操作
自定义聚合方式可以:
每个列应用不同的聚合方式
一个列应用多个聚合方式
df = pd.DataFrame({
'name': ['张三','李四','王五','李四','王五','王五','赵六'],
'chinese': [18, 53, 67, 63, 59, 70, 94],
'math': [82, 63, 41, 59, 46, 39, 58],
'english': [68, 52, 80, 86, 60, 98, 64],
'test': ['一','一','一','二','二','三','一']
})
# 使用自定义聚合方式实现
df.groupby('name').agg(sum)
# 聚合参数是列表,给每一列同时应用多个聚合函数
# 列表参数函数可以有多种不同写法:直接写函数名(容易出错),函数名写成字符串,ndarray数组函数,如果一种写法出错,尝试换其他写法
df.groupby('name').agg([sum, 'mean', np.min])
# 将聚合列索引改为自定义方式,元组实现
df.groupby('name')['chinese', 'math'].agg([('求和', sum), ('平均值', 'mean'), ('最小值', min)])
# 聚合参数是字典,每列应用一个不同聚合函数,或者每列应用多个不同的聚合函数
# 语文列聚合函数:求和
df.groupby('name').agg({'chinese': sum})
# 语文列聚合函数:求和,平均值
df.groupby('name').agg({'chinese': [sum, 'mean']})
# 选中的多个列,每列都应用不同的多个聚合函数
df.groupby('name').agg({'chinese': [sum, 'mean'], 'math': [np.min, np.max]})
聚合参数是自定义函数
用于一些较为复杂的聚合工作
- 自定义聚合函数要比系统自带的、经过优化的函数慢得多。
- 因为在构造中间分组数据块时存在非常大的开销(函数调用、数据重排等)
def aaa(x):
return x.max() - x.min()
df.groupby('name').agg(aaa)
# 匿名函数实现
df.groupby('name').agg(lambda x: x.max() - x.min())
# 定一个 top 函数,返回 DataFrame 某一列中 n 个最大值
def top(df, n=2, column='chinese'):
return df.sort_values(by=column, ascending=False)[:n]
# 因为分组了 显示每个分组的前2;; 区别于之前学习地apply;;因为此时传递进入的是groupby的数据
df.groupby('name').apply(top)
df.groupby('name').apply(top,n=1)
过滤数据
例子:输出所有语文考试平均分及格的数据
def bbb(x):
return x['chinese'].mean() >= 60
df.groupby('name').agg(bbb) # 测试出错
df.groupby('name').apply(bbb)
df.groupby('name').filter(bbb)
# 输出所有语文平均分及格的学生
df.groupby('name').filter(bbb).groupby('name').mean()
例子:将学生某科成绩按由低到高排序,并返回需要的个数
返回语文成绩最低的前三条数据
返回所有同学语文成绩最低的1次考试成绩
返回所有同学数学成绩最低的2次考试成绩
# 自定义函数实现上面功能,高级
def top(x, p='chinese', n=3, a=True):
"""
自定义函数实现DataFram对象排序输出功能.
x:传入的DataFrame对象
n:获取前几个值
p:按df对象的哪一列排序
a: 默认True升序,False降序
"""
return x.sort_values(by=p, ascending=a)[:n]
# 所有同学语文成绩最低的前3名
top(df)
# 数学倒数第一的同学
top(df, p='math', n=1)
# 英语成绩最高的2位同学
top(df, p='english', n=2, a=False)
使用apply方式调用函数实现
上面是所有数据行操作,下面是分组后的数据操作
df.groupby('name').apply(top)
# 自定义函数参数设置
# 用于操作的数据表不需要手动传入,如果手动传入会报参数重复错误
df.groupby('name').apply(top, p='math', n=2, a=False)
禁止分组键
分组键会跟原始对象的索引共同构成结果对象中的层次化索引
将group_keys=False传入groupby即可禁止该效果
# name,test都是行索引,返回三围,将group_keys=False传入groupby即可禁止该效果
df.groupby(['name','test']).sum()
# 删除,删除分组带来的外层索引
df.groupby('name').apply(top, n=2, p='math')
df.groupby('name', as_index=False).apply(top, n=2, p='math')
df.groupby('name', group_keys=False).apply(top, n=2, p='math')
关于groupby调用describe()方法
df.describe()
df['chinese']
df['chinese'].describe()
df.groupby('name')['chinese'].mean()
df.groupby('name')['chinese'].describe()
df.groupby('name')['chinese'].describe().stack() # 列转行
df.groupby('name')['chinese'].describe().unstack().unstack() # 行转列
df.groupby('name')['chinese'].describe().T
将DataFrame分组后应用describe()函数
dataframe分组后之所以可以进行describe操作,原因是生成的结果是层次化索引(相当于3维数据)
本文为原创文章,转载请标明出处

浙公网安备 33010602011771号