重塑和轴向旋转
- 有许多用于重新排列表格型数据的基础运算,这些函数也成为重塑或轴向旋转运算
- 数据重塑和轴向选择操作:表示转换一个表格或向量的结构,使其适合于进一步的分析
重塑层次化索引
层次化索引喂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维数据)