【Task04】Pandas学习打卡

前言

在上一章,我们主要学习了:

  • 通过不同索引相关方法访问 SeriesDataFrame 中的数据
  • 索引本身的属性方法

在本次学习,我们主要关注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种组合:

AB结果
Series标量Series
SeriesSeriesDataFrame
SeriesDataFrameDataFrame
DataFrame标量DataFrame
DataFrameSeriesDataFrame
DataFrameDataFrameDataFrame

我们在上面举的例子就是表格中的前三种情况。

练一练

>>> 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库的数据读取、数据获取、数据拼接、数...
Pandas是一个开源的第三方Python库,从Numpy和Matplotlib的基...
整体流程登录天池在线编程环境导入pandas和xrld操作EXCEL文件...
 一、numpy小结             二、pandas2.1为...
1、时间偏移DateOffset对象DateOffset类似于时间差Timedelta...
1、pandas内置样式空值高亮highlight_null最大最小值高亮背景...