【Task04】Pandas学习打卡
作者:互联网
前言
在上一章,我们主要学习了:
- 通过不同索引相关方法访问 Series 和 DataFrame 中的数据
- 索引本身的属性和方法
在本次学习,我们主要关注pandas中分组的相关知识,主要有:
- 分组的概念及分组对象 GroupBy
- 分组的三大操作:聚合 、变换 、过滤
- 跨列分组的实现
通过学习分组,我们能按照目标数据的不同类别进行“分堆”并以此进行下一步处理,其实可以理解成在行列定位后更精确的操作。
一、分组模式及其对象
为什么要用分组,这里以‘learn_pandas数据集’为例:
>>>df = pd.read_csv('data/learn_pandas.csv')
>>>df
假设我们要对男同学和女同学的数据分别操作,我们当然可以通过索引去分别访问:
#得到所有女同学的信息
df_female = df[df.Gender == 'Female']
df_female
#得到所有男同学的信息
df_male = df[df.Gender == 'Male']
df_male.head()
我们可以利用df_female和df_male分别进行下一步数据的分析,但如果划分的列不止一个呢?比如根据年级和性别同时划分,那就要新建8个DF数据进行存储然后再进行访问,这无异于增大难度,pandas中的分组恰好能高效地解决上述情况。
1.分组的一般模式
分组采用的是groupby()方法
,Series和DataFrame均有这个方法,以DataFrame为例,举例说明:
>>> gb = df.groupby('Gender')
>>> res = gb.count()
>>> print(type(res))
>>> res
我们通过形如df.groupby(A)
的方式可以快速对数据进行分组,并且可以对分组后的数据进行操作,如上我们对分组后的数据使用了内置的count()方法
,返回了一个DF类型的按性别分类后对所有其他列的计数统计。
我们当然可以通过索引.[B]
限定列的范围:
>>> res = gb['Height'].count()
>>> print(type(res))
>>> res
<class 'pandas.core.series.Series'>
Gender
Female 132
Male 51
Name: Height, dtype: int64
返回的是Series类型的按性别分类后对身高的计数,注意采用这种使用方式,会自动忽略缺失值。
2.分组依据的本质
我们也可以利用除了单个列名以外其它的方式对DF数据进行分组:
利用多个列分组
>>> df.groupby(['Gender','Grade'])['Height'].count()
>>> print(type(res))
>>> res
<class 'pandas.core.series.Series'>
Gender Grade
Female Freshman 37
Junior 40
Senior 36
Sophomore 19
Male Freshman 10
Junior 14
Senior 17
Sophomore 10
Name: Height, dtype: int64
分成了8组,返回类型为Series。
利用list进行分组
#利用np的随机方法生成一个与df的列高相等的list
>>> my_list = np.random.choice(list('XYZW'),df.shape[0])
>>> df.groupby(my_list)['Height'].count()
>>> print(type(res))
>>> res
<class 'pandas.core.series.Series'>
W 49
X 42
Y 43
Z 49
Name: Height, dtype: int64
分成了4组,返回类型仍为Series,可以总结返回类型仅与保留的列有关,与利用的分组数量无关。
利用布尔列表进行分组
>>> df.groupby(df.Height > 170)['Height'].count()
Height
False 147
True 36
Name: Height, dtype: int64
这里按真假分成了2组,返回类型仍为Series。
利用组合条件进行分组
>>> df.groupby([df.Height > 170,my_list])['Height'].count()
Height
False W 32
X 31
Y 45
Z 39
True W 11
X 10
Y 9
Z 6
Name: Height, dtype: int64
>>> df.groupby(['Gender',my_list])['Height'].count()
Gender
Female W 28
X 30
Y 38
Z 36
Male W 15
X 11
Y 16
Z 9
Name: Height, dtype: int64
>>> df.groupby(['Gender',df.Height > 170])['Height'].count()
Gender Height
Female False 131
True 1
Male False 16
True 35
Name: Height, dtype: int64
这里我列举了3种组合情况,可以注意到分组条件是存在先后顺序的,比如上面的例子性别在前,真假在后。
练一练
>>> def func(x):
>>> if x>b:
>>> return 'high'
>>> elif x < a:
>>> return 'low'
>>> elif x>=a and x <=b:
>>> return 'normal'
>>> s_w = df.Weight
>>> a = s_w.quantile(0.25)
>>> b = s_w.quantile(0.75)
>>> res = s_w.apply(func)
>>> df.groupby(res)['Height'].mean()
Weight
high 174.935714
low 153.753659
normal 161.883516
Name: Height, dtype: float64
3.Groupby对象
>>> gb = df.groupby('Gender')[['Height','Weight']]
>>> print(gb)
pandas.core.groupby.generic.DataFrameGroupBy
我们可以看到DataFrame数据的groupby()方法
返回的是DataFrameGroupBy类型。
DataFrameGroupBy类型属于Group对象的一种,它有如下性质:
>>> print(gb.ngroups)
2
>>> print(gb.groups.keys())
dict_keys(['Female', 'Male'])
>>> print(gb.size())
Gender
Female 141
Male 59
dtype: int64
>>> gb.describe()
练一练
>>> column_name = ['School','Grade']
>>> pd.DataFrame(list(df.groupby(column_name).groups.keys()),columns=column_name)
二、聚合方法
先介绍分组三大操作的第一种,顾名思义就是对分组后之后的每组的数据进行处理。
1.内置聚合方法
Groupby对象有一些提前定义好的内置聚合方法供我们使用:
max/min/mean/median/count/all/any/idxmax/idxmin/mad/nunique/skew/quantile/sum/std/var/sem/size/prod
其实在上一节的describe()方法
里我们已经见到大多数的内置聚合方法了,我们再举几个例子:
>>> print(gb.var())
Height Weight
Gender
Female 25.542739 29.224655
Male 49.681137 60.412648
>>> print(gb.size())
Gender
Female 141
Male 59
dtype: int64
>>> gb.sum()
Height Weight
Gender
Female 21014.0 6469.0
Male 8854.9 3929.0
练一练
#自定义DF结构数据
>>> df_demo = pd.DataFrame({'A':[False,True,False,True,True,False],'B':list('ababcc')})
>>> print(df_demo)
A B
0 False a
1 True b
2 False a
3 True b
4 True c
5 False c
>>> gb = df_demo.groupby('B')['A']
>>> gb.describe()
#组内是否全为True
>>> gb_demo.all()
B
a False
b True
c False
Name: A, dtype: bool
##组内是否存在True
>>> gb_demo.any()
B
a False
b True
c True
Name: A, dtype: bool
#初始化数据
>>> gb = df.groupby('Gender')['Height']
#mean absolute deviation 平均绝对离差
>>> gb.mad()
Gender
Female 4.088108
Male 5.394617
Name: Height, dtype: float64
#unbiased skew 无偏偏度
>>> gb.skew()
Gender
Female -0.219253
Male 0.437535
Name: Height, dtype: float64
#standard error 标准误差
>>> gb.sem()
Gender
Female 0.439893
Male 0.986985
Name: Height, dtype: float64
#总乘积
>>> gb.prod()
Gender
Female 4.232080e+290
Male 1.594210e+114
Name: Height, dtype: float64
这里稍微介绍一下标准误差:
标准误差不是测量值的实际误差,也不是误差范围,它只是对一组测量数据可靠性的估计。标准误差小,测量的可靠性就大一些;反之,则测量的可靠性要小一些。
即标准误则随着样本数(或测量次数)n的增大逐渐减小。
2.agg方法
agg()方法解决了如下的问题:
- 同时使用多个方法
- 对特定的列使用特定的聚合方法
- 使用自定义的聚合方法
- 对聚合结果进行自定义命名
同时使用多个方法
通过用将内置方法的名称放到一个list中,可以同时使用:
>>> gb.agg(['max','min','count'])
对特定列使用指定的聚合方法
通过字典方式可以对特定列使用指定的聚合方法:
my_dict = {'Height':['max','count'],'Weight':'min'}
gb.agg(my_dict)
这里要注意,针对指定列时,仍需要用list把方法名括起来。
两种方法:
直接运用agg([])
对指定列运用agg({列名1:[],列名2:[]})
练一练
>>> gb = df.groupby('Gender')[['Height','Weight']]
>>> gb.agg({'Height':['sum','idxmax','skew'],'Weight':['sum','idxmax','skew']})
使用自定义方法
>>> gb.agg(lambda x:x.mean())
等价于:
>>> def func(x):
>>> return x.mean()
>>> gb.agg(func)
其中,agg中的x参数表示的是每组数据,其类型可能为Series或DataFrame,在上面的例子中为Series,并且是先遍历完一个列之后再遍历下一个列:
练一练
>>> gb.agg(['count','mean','std','min',lambda x:x.quantile(0.25),'quantile',lambda x:x.quantile(0.75),'max'])
对聚合结果进行自定义命名
我们可以注意到,相比于内置聚合方法,目前的自定义方法没有默认名字,所以来设置一下名字:
>>> gb.agg([('my_max', lambda x: x.max()), ('my_min',lambda x: x.min())])
当然我们也可以针对不同列使用不同的聚合方法:
>>> gb.agg({'Height': [('my_max', lambda x: x.max())], 'Weight': [('my_min',lambda x: x.min())]})
注意自定义方法需要用tuple括起来(name,函数),且当只使用一个方法时,也要在外面用中括号括起来:
#合法
>>> gb.agg([(方法名, 方法)])
#非法
>>> gb.agg((方法名, 方法))
三、变换和过滤
相比于聚合,变换并不会改变数据的shape(指的是分组前的DF和分组后的Gb对象进行变换操作后)。
1.变换方法与transfrom方法
pandas最常用的内置变换方法是累计方法:cumcount/cumsum/cumprod/cummax/cummin
>>> gb.cummin().head()
注意,这里出现了NaN,是因为变换方法在默认情况下会自动略过缺失值。
练一练
GroupBy.rank(method='average', ascending=True, na_option='keep', pct=False, axis=0)
>>> gb.rank().head()
返回的是从1开始,每个元素的排名位置,默认为升序,出现*.5是因为遇到相同的n个值,会使用默认的‘average’方法,将其返回值设置为从当前位置开始的计算n个位置的和然后除以n,所以会得到.5。
同agg()方法
可以自定义聚合方法一样,我们可以使用transfrom()方法
自定义变换方法:
>>> def func(x):
>>> print(type(x),x.shape,x.name)
>>> return x+10
>>> gb.transform(func).head(10)
我们可以看到transform中的参数x有2种形式,分别是不同分组类型的数据和每个分组下不同列的数据。我们看一下是哪种数据主导自定义函数:
我们经过上面两个例子可以看出,是第二种类型的数据,也就是每个分组下的不同列信息主导自定义函数的返回操作。
练一练
>>> def add10(x):
>>> return x+10
>>> def min10(x):
>>> return x-10
>>> def my_transform(gb,dict):
>>> return gb.transform((lambda y:(lambda x:y[x.name](x)))(dict))
>>> my_dict = {'Height':add10,'Weight':min10}
#最理想的状态,使用者传入gb分组数据和dict数据(包括对每个列使用的函数)
>>> res = my_transform(gb,my_dict)
>>> res.head()
2.组索引与过滤
最后一种操作是过滤,它用于过滤组:
>>> gb.filter(lambda x:x['Height'].max() > 180).head()
上面的例子用来过滤组的最高身高小于等于180的组。
练一练
>>> df.loc[:,['Height','Weight']].head()
>>> df.filter(items=['Height','Weight']).head()
四、跨列分组
1.apply的引入
apply()方法
在之前我们就已经接触过,利用它可以对行列数据进行操作,在GroupBy对象中,它是对组信息进行按组操作。
2.apply的使用
可以看到,它的参数x是每组的数据。与上面的其他三大类操作的自定义方法不同,apply方法
的返回的数据类型根据自定义方法中返回值类型和原数据本身类型的组合的不同而不同:
简单来说,假设分组的列类型为A,可能为Series类型或DataFrame类型;自定义方法的返回值类型为B,可能为标量或Series类型或DataFrame类型。
它们有6种组合:
A | B | 结果 |
---|---|---|
Series | 标量 | Series |
Series | Series | DataFrame |
Series | DataFrame | DataFrame |
DataFrame | 标量 | DataFrame |
DataFrame | Series | DataFrame |
DataFrame | DataFrame | DataFrame |
我们在上面举的例子就是表格中的前三种情况。
练一练
>>> def func(x):
>>> s1 = pd.Series([1,2],index=['a','b'])
>>> s2 = pd.Series([3,4],index=['c','d'])
>>> # s2 = pd.Series([3,4],index=['a','b'])
>>> if x.name[1]==1:
>>> return s1
>>> return s2
>>> gb.apply(func)
练一练
def func(x):
df1 = pd.DataFrame(np.ones((2,2)), index = ['a','b'], columns=pd.Index([('w','x'),('y','z')]))
df2 = pd.DataFrame(np.ones((2,2)), index = ['a','b'], columns=pd.Index([('a','b'),('c','d')]))
if x.name[1]==1:
return df1
return df2
gb.apply(func)
def func(x):
df1 = pd.DataFrame(np.ones((2,2)), index = ['a','b'], columns=pd.Index([('w','x'),('y','z')]))
df2 = pd.DataFrame(np.ones((2,2)), index = ['c','d'], columns=pd.Index([('w','x'),('y','z')]))
if x.name[1]==1:
return df1
return df2
gb.apply(func)
练一练
五、练习
Ex1:汽车数据集
1.
def func(x):
s1 = x.mean()
s2 = x.std()/x.mean()
s3 = x.count()
return pd.Series({'mean':s1,'CoV':s2,'count':s3})
s_country = df.Country.value_counts()
gb = df.groupby('Brand')
df_demo = gb.filter(lambda x:s_country[x.Country] >= 2)
gb = df_demo.groupby('Country')['Price']
res = gb.apply(func)
res
2.
df.groupby(list('0'*20+'1'*20+'2'*20))['Price'].mean()
0 9069.95
1 13356.40
2 15420.65
Name: Price, dtype: float64
3.
aimColumns = ['Price','HP']
gb = df.groupby('Type')[aimColumns]
res = gb.agg(['max','min'])
res
new_col= res.columns.map(lambda x:x[0]+'_'+x[1])
res.columns = new_col
res
4.
aimColumns = ['HP']
gb = df.groupby('Type')[aimColumns]
res = gb.transform(lambda x:(x-x.min())/(x.max()-x.min()))
print(res.head(10))
HP
0 1.00
1 0.54
2 0.00
3 0.58
4 0.80
5 0.38
6 0.54
7 0.22
8 0.54
9 0.20
5.
aimColumns = ['Disp.','HP']
gb = df.groupby('Type')[aimColumns]
gb.corr()
Ex2:实现transform函数
class my_groupby:
def __init__(self, my_df, group_cols):
#准备工作
self.my_df = my_df.copy()
#self.groups保存group唯一的组合 2种情况 Female+Male or Gender+Grade
self.groups = my_df[group_cols].drop_duplicates()
#如果是Series对象 就升维 这里判断group_cols是一列还是多列
if isinstance(self.groups, pd.Series):
#Convert Series to DataFrame 转换成DF 这里可以利用传入做限制
self.groups = self.groups.to_frame()
#group为DF型
#保存列名 List型
self.group_cols = self.groups.columns.tolist()
#把DF型的groups用字典存储下来
self.groups = {i: self.groups[i].values.tolist() for i in self.groups.columns}
#初始化transform_col
self.transform_col = None
def __getitem__(self, col):
self.pr_col = [col] if isinstance(col, str) else list(col)
return self
def transform(self, my_func):
self.num = len(self.groups[self.group_cols[0]])
L_order, L_value = np.array([]), np.array([])
for i in range(self.num):
group_df = self.my_df.reset_index().copy()
for col in self.group_cols:
group_df = group_df[group_df[col]==self.groups[col][i]]
group_df = group_df[self.pr_col]
if group_df.shape[1] == 1:
group_df = group_df.iloc[:, 0]
group_res = my_func(group_df)
if not isinstance(group_res, pd.Series):
group_res = pd.Series(group_res,index=group_df.index,name=group_df.name)
L_order = np.r_[L_order, group_res.index]
L_value = np.r_[L_value, group_res.values]
self.res = pd.Series(pd.Series(L_value, index=L_order).sort_index().values,index=self.my_df.reset_index().index, name=my_func.__name__)
return self.res
参考文献
1.python之匿名函数以及在内置函数中的使用
https://www.cnblogs.com/lpgit/p/10597294.html
2.关于Python中rank()函数的理解
https://blog.csdn.net/justinlonger/article/details/90646111
标签:Pandas,df,res,self,Height,gb,group,打卡,Task04 来源: https://blog.csdn.net/Xiao_Spring/article/details/111600321