Pandas数据分组与聚合

# 分组统计是数据分析中的重要环节:

# 1-数据分组:GroupBy的原理和使用方法;
# 2-聚合运算:学会分组数据的聚合运算方法和函数使用; 类似于 SQL思想
# 3-分组运算:重点 apply方法的使用
# 4-数据透视表:学会构建数据透视表和交叉表

# 一,GroupBy:
# 步骤:split-apply-combine
# 举例:小费 tips
import pandas as pd
import numpy as np
import seaborn as sns
from pandas import Series,DataFrame

# tips=sns.load_dataset('tips')  # 从 github仓库里下载 https://github.com/mwaskom/seaborn-data
tips=pd.read_csv('tips.csv')
tips.head(20)
total_bill tip sex smoker day time size
0 16.99 1.01 Female No Sun Dinner 2
1 10.34 1.66 Male No Sun Dinner 3
2 21.01 3.50 Male No Sun Dinner 3
3 23.68 3.31 Male No Sun Dinner 2
4 24.59 3.61 Female No Sun Dinner 4
5 25.29 4.71 Male No Sun Dinner 4
6 8.77 2.00 Male No Sun Dinner 2
7 26.88 3.12 Male No Sun Dinner 4
8 15.04 1.96 Male No Sun Dinner 2
9 14.78 3.23 Male No Sun Dinner 2
10 10.27 1.71 Male No Sun Dinner 2
11 35.26 5.00 Female No Sun Dinner 4
12 15.42 1.57 Male No Sun Dinner 2
13 18.43 3.00 Male No Sun Dinner 4
14 14.83 3.02 Female No Sun Dinner 2
15 21.58 3.92 Male No Sun Dinner 2
16 10.33 1.67 Female No Sun Dinner 3
17 16.29 3.71 Male No Sun Dinner 3
18 16.97 3.50 Female No Sun Dinner 3
19 20.65 3.35 Male No Sat Dinner 3
# 将序列按照单个分组键分组,并聚合计算:
# 1,groupby()可以被 Series调用,也可以被 DataFrame调用。
grouped=tips['tip'].groupby(tips['sex'])  # 对 tip Series,按照 sex序列的值进行分组
grouped=tips.groupby(tips['sex'])         # 对 tips DataFrame,按照 sex序列的值进行分组
grouped  # <pandas.core.groupby.generic.SeriesGroupBy object at 0x0000026FF174BC08>  GroupBy对象 可迭代对象
grouped.mean()  # 平均值  聚合运算
grouped.sum()   # 求和
# 将序列按照多个分组键分组,并聚合计算:
data_mean=tips['tip'].groupby([tips['sex'],tips['time']]).mean()
data_mean  # type(data_mean)  # 多层索引的序列 pandas.core.series.Series
data_mean.plot(kind='barh')   # 男性晚餐时候小费给的平均值高
# GroupBy对象是可迭代对象,其构造为一组二元元组:
for name,group in tips['tip'].groupby(tips['sex']):
    print(name)
    print(group)
    
# 可查看各分组的大小
tips.groupby(tips['sex']).size()




# 2,分组方式:

# 2-1)按序列分组:
grouped=tips['tip'].groupby(tips['sex'])  # 对 tip Series,按照 sex序列的值进行分组
grouped=tips.groupby(tips['sex'])         # 对 tips DataFrame,按照 sex序列的值进行分组

# 2-2)按列名分组:
smoker_mean=tips.groupby('smoker').mean()
smoker=tips.groupby('somker',group_keys=False)['tip']
smoker_mean=tips.groupby(['sex','smoker']).mean()  # 按多个列名组合分组
smoker_mean=tips.groupby(['sex','smoker'],as_index=False).mean()  # 按多个列名组合分组   as_index=False:不以分组键作为索引

size_mean1=tips.groupby('size')['tip'].mean()  # 等效于:
size_mean2=tips['tip'].groupby(tips['size']).mean()
size_mean1==size_mean2  # 返回全是True的布尔数组

# 2-3) 按行索引分组:
df=DataFrame(np.arange(16).reshape(4,4),index=['a','b','a','b'])
df.groupby(df.index).mean() 

# 2-4)按列表或元组分组: # 相当于先给索引按照列表重命名,然后按行索引分组。
df=DataFrame(np.arange(16).reshape(4,4))
list1=['a','b','a','b']
df.groupby(list1).mean() 

# 2-5)按照字典分组:  # 相当于先给dataframe的索引重命名,再按新索引分组。
# 当要分组的列或行索引的值不明确时,需要使用字典指定
df=DataFrame(np.arange(16).reshape(4,4),index=['a','b','A','B'])
dict1={
    'a':'one',
    'A':'one',
    'b':'two',
    'B':'two'
}
df.groupby(dict1).mean()

# 2-6)按函数分组:
# 原理类似于字典,通过映射关系进行分组,但更灵活。
df=DataFrame(np.random.randn(4,4))
df
df.groupby(df[3].map(lambda x:'a' if x>=0 else 'b')).sum()  #看结果就明白了

# 2-7) 对于层次化索引,按 level级别进行分组:
df=DataFrame(np.arange(16).reshape(4,4),
            index=[['one','one','two','two'],['a','b','a','b']],
            columns=[['apple','apple','orange','orange'],['red','green','red','green']])
df
# df.groupby(level=1).sum()
# df.groupby(level=1,axis=1).sum()
# df.groupby(level=0).sum()
df.groupby(level=0,axis=1).sum()




# 3,聚合运算              注:空值不参与计算!  返回聚合后的序列
# 对分组后的数据进行计算,产生标量值的转换过程叫聚合运算,上面的 mean(),sum()都是。

# 3-1) 常用的聚合函数:
# count       计数
# sum         求和
# mean        求平均值
# median      求算术中位数
# std         求标准差
# var         求方差
# min,max    求最小值,最大值
# prod        求积
# first,last  求第一个值,求最后一个值
# quantile    分位数计算

max_tip=tips.groupby('sex')['tip'].max()
max_tip  # 序列
max_tip.plot(kind='bar')

# 3-2) 自定义聚合函数: grouped.aggregate(f) 或 grouped.agg(f)     aggregate--聚合
# se.agg(f)与 se.map(f)的不同点在于:agg是Grupby对象的聚合函数,而 map是Series的矢量化函数。
# 因而,agg里的函数 f一般也是由聚合函数组成的。

def get_range(x):
    '''接收一个数字序列,或数字列表,得到数字的范围'''
    return x.max()-x.min()

# tips_range=tips.groupby('sex')['tip'].agg(get_range)
tips_range=tips.groupby('sex')['tip'].agg(lambda x:x.max()-x.min())
tips_range


# 4,多函数应用:
# 4-1) 一列多函数  agg([f1,f2,f3...])
# get_range=lambda x:x.max()-x.min()
def get_range(x):
    '''接收一个数字序列,或数字列表,得到数字的范围'''
    return x.max()-x.min()

tips.groupby(['sex','smoker'])['tip'].agg(['mean','std',get_range])  #自定义的函数不能使用'', 默认列名为函数名
tips.groupby(['sex','smoker'])['tip'].agg([('tip_mean','mean'),('range',get_range)])  #指定列名

# 4-2) 多列多函数:  产生列的层次化索引
tips.groupby(['day','time'])['tip','total_bill'].agg([('tip_mean','mean'),('range',get_range)])  # 将来版本要用列表替代元组?


# 4-3) 不同列,不同函数:   使用字典映射
tips.groupby(['day','time'])['total_bill','tip'].agg({'total_bill':['sum','mean'],'tip':'mean'}) # 将要被废弃




# 5, 分组运算: 

# 运行下面代码,体会 transform 和 apply的不同效果:
tips.groupby('sex').transform('mean')
tips.groupby('sex')['tip'].transform('mean')
tips.groupby('sex').apply(lambda x:x.mean()) 
tips.groupby('sex')['tip'].apply(lambda x:x.mean())
       
# 速记:
tips.groupby('sex').transform('mean')                         # 只计算可以计算的列,比如3个可计算列,并返回新的 由3个列组成的 DataFrame
tips.groupby('sex')['tip'].transform('mean')                  # 只计算 tip列,返回一个 Series.
df.groupby('sex').apply(lambda x:x.fillna(x.mean()))          # 返回一个 对【所有数值列】缺失值填充后的 DataFrame  
df.groupby('sex')['math'].apply(lambda x:x.fillna(x.mean()))  # 返回一个 只对【Math】列缺失值填充后的Series

# grouped.perform(),grouped.apply(),都不改变原数据结构,如果想要做改变,可以赋值,比如:
tips['tip_mean_by_sex']=tips.groupby('sex')['tip'].transform('mean')   # 增加一个新的列 tip_mean_by_sex


# 5-1) tips.groupby('sex')['tip'].transform('mean').transform()方法:   返回
# 对于小费数据集 tips,新建一列用于存放男性和女性小费的平均值。
# 1)常用方法是,先分组聚合运算,再 merge按 sex键合并,出现一个新的列。
tip_mean_by_sex=tips.groupby('sex')['tip'].mean()
tip_mean_by_sex
tip_mean_by_sex_df=DataFrame(tip_mean_by_sex)  # 注意,参数是 tips.groupby('sex')['tip'].mean() 不是 tips.groupby('sex')['tip']
tip_mean_by_sex_df  # 此时行索引的 name为 sex,行索引的值为[Female,Male]

new_tips=pd.merge(tips,tip_mean_by_sex_df,left_on='sex',right_index=True,suffixes=('','_mean_by_sex'),how='left')  # 指定left,tips的索引顺序就不会改变
new_tips.head(10)

# 2) 使用 transform('mean')方法将参数里的函数运算结果分布到每一行,非聚合函数,dataframe的矢量化函数:
new_tips=tips.copy()
new_tips['tip_mean_by_sex']=tips.groupby('sex')['tip'].transform('mean')   # 增加一个新的列 tip_mean_by_sex
new_tips.head(10)


# 5-2)df.groupby('sex').apply()方法:   返回DataFrame数据
# 更加强大:
# 计算根据性别分组后的小费金额前 5名的 DataFrame数据:默认结果以分组键作为行索引,可以用 groupby()里使用group_keys=False来改变:
tips.groupby('sex').apply(lambda x:x.sort_values(by='tip',ascending=False)[:5])
tips.groupby('sex',group_keys=False).apply(lambda x:x.sort_values(by='tip',ascending=False)[:5])
# groupby()的 group_keys=False 参数只有在调用 apply()的时候好使。


# 对缺失值的处理,前面数据处理那部分,有使用平均值进行填充缺失值的例子,如:
data={
    'name':['张三','李四',np.nan,'王五','小明','马六'],
    'sex':['female','female','male','male','male','female'],
    'math':[67,77,np.nan,82,90,np.nan],
    'English':[67,77,np.nan,82,90,np.nan]
}

df=DataFrame(data)
df.fillna(df['math'].mean())  # 使用 math列的平均值填充缺失值
df.fillna(df['English'].mean())  # 使用 English列的平均值填充缺失值
df.groupby('sex').apply(lambda x:x.fillna(x.mean()))  # 根据性别计算每个性别的 math和 english的平均数,来填充不同性别对应的缺失值.
df.groupby('sex')['math'].apply(lambda x:x.fillna(x.mean()))  # 根据性别计算每个性别的 math的平均数,来填充不同性别对应的缺失值.





# 6,数据透视表
# 关于 Excel数据透视表的使用教程,请见: https://www.zhihu.com/question/24341252

# df.pivot(values=要计算的列名, index=行名, columns=列名, aggfunc='sum', margins=True)  # 由 df调用
# pd.cross_table(index=tips['day'],columns=tips['size'])                              # 由 pd调用,传入 df的两个整列作为行列索引


# 6-1)透视表:
# pandas里也有数据透视表功能,pivot_table 函数。
tips.pivot_table(values='tip',index='sex',columns='smoker')                 # 默认计算 mean()
tips.pivot_table(values='tip',index='sex',columns='smoker',aggfunc='sum')   # 默认计算 mean(), aggfunc='sum' 指定计算 sum
tips.pivot_table(values='tip',index='sex',columns='smoker',aggfunc='sum',margins=True)   # margins=True 对其他列做小计

# 用 groupby()来实现:
tips.groupby(['sex','smoker'])['tip'].mean().unstack()  # 按多个列名组合分组  unstack()的默认参数为 1,表示操作内层行索引。
tips.groupby(['sex','smoker'])['tip'].sum().unstack()   #  sum

sex_smoker=tips.groupby(['sex','smoker'])['tip'].sum().unstack()   #  sum
sex_smoker['All']=sex_smoker['No']+sex_smoker['Yes']    #增加一列

sex_smoker=sex_smoker.append({'No':sex_smoker['No'].sum(),'Yes':sex_smoker['Yes'].sum()},ignore_index=True)  # 增加一行
sex_smoker.index.name='Sex'   # 恢复行索引名称
sex_smoker.index=['Female','Male','All']  # 恢复原来的行索引
sex_smoker
# 所以,使用透视表更简单些


# 6-1)交叉表:
# 用于计算分组频次与频率的特殊透视表
cross_table=pd.cross_table(index=tips['day'],columns=tips['size'])  # 频次
cross_table
df=cross_table.div(cross_table.sum(axis=1),axis=0)                  # 频率   每行的和为1
df
# 频率频次堆积图:
df.plot(kind='bar',stacked=True)
发布了37 篇原创文章 · 获赞 12 · 访问量 1648

猜你喜欢

转载自blog.csdn.net/qq_24937551/article/details/105280255