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
 
 stateOhioColorado
 colorGreenRedGreen
key1key2   
a1 0 1 2
2 3 4 5
b1 6 7 8
2 9 10 11
# 用分组实现
# 默认传入的是这个列的索引的所有值,而不是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维数据)

  

 

  

 

 

 

posted @ 2020-06-03 21:53  亚洲哈登  阅读(216)  评论(0编辑  收藏  举报