金融量化学习---Python, MySQL, Pandas

这里用来记录一些在金融领域,尤其是银行相关的资金、债券、票据中应用到的数据管理与分析, 编程等心得或笔记,以及个人的一点小小兴趣(易经八卦、藏密禅修)等

导航

Pandas中的GroupBy及agg详解


pandas提供了一个灵活高效的groupby功能,它使你能以一种自然的方式对数据集进行切片、切块、摘要
等操作。根据一个或多个键(可以是函数、数组或DataFrame列名)拆分pandas对象。计算分组摘要统
计,如计数、平均值、标准差,或用户自定义函数。对DataFrame的列应用各种各样的函数。应用组内转换
或其他运算,如规格化、线性回归、排名或选取子集等。计算透视表或交叉表。执行分位数分析以及其他分
组分析。
groupby()是一个分组函数,对数据进行分组操作的过程可以概括为:split-apply-combine三步:

  1. 按照键值(key)或者分组变量将数据分组。
  2. 对于每组应用我们的函数,这一步非常灵活,可以是python自带函数,可以是我们自己编写的函数。
  3. 将函数计算后的结果聚合。

  返回值:返回重构格式的DataFrame,特别注意,groupby里面的字段内的数据重构后都会变成索引
  groupby(),一般和sum()、mean()一起使用,如下例:

官网:https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html

groupby分组函数:

DataFrame.groupby(by=None, axis=0, level=None, as_index=True, sort=True, group_keys=True, squeeze=False, **kwargs)

by : 接收映射、函数、标签或标签列表;用于确定聚合的组
axis : 接收 0/1;用于表示沿行(0)或列(1)分割。
level : 接收int、级别名称或序列,默认为None;如果轴是一个多索引(层次化),则按一个或多个特定级别分组
as_index : 接收布尔值,默认Ture;Ture则返回以组标签为索引的对象,False则不以组标签为索引

基本操作

在进行对groupby函数进行学习之前,首先需要明确的是,通过对DataFrame对象调用groupby()函数返回的结果是一个DataFrameGroupBy对象,而不是一个DataFrame或者Series对象,所以,它们中的一些方法或者函数是无法直接调用的,需要按照GroupBy对象中具有的函数和方法进行调用。

import pandas as pd
import numpy as np

df = pd.DataFrame({'key1':list('aabba'),
                  'key2': ['one','two','one','two','one'],
                  'data1': [8,6,2,4,3],
                  'data2': [6,9,5,2,-7]})

  key1 key2  data1  data2
0    a  one      8      6
1    a  two      6      9
2    b  one      2      5
3    b  two      4      2
4    a  one      3     -7 

grouped = df.groupby('key2')
print(type(grouped))
print(grouped)

#输出结果如下:
<class 'pandas.core.groupby.generic.DataFrameGroupBy'>
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x00000292E0778B50>

普通分组,单值分组

按key1分组并求均值

print(df.groupby('key1').mean(),'\n')

         data1     data2
key1                    
a     5.666667  2.666667
b     3.000000  3.500000 

print(df.groupby('key1',as_index=False).mean(),'\n')
#注意使用as_index=False和不使用的区别
  key1     data1     data2
0    a  5.666667  2.666667
1    b  3.000000  3.500000

#功能与上一句相同:print(df.groupby(['key1'],as_index=False).mean(),'\n')

普通分组,多值分组

按states,years分组并求均值

states=np.array(['Ohio','California','California','Ohio','Ohio'])
years=np.array([2005,2005,2006,2005,2006])
print(df['data1'].groupby([states,years]).mean(),'\n')

#输出结果如下:
California  2005    6
            2006    2
Ohio        2005    6
            2006    3

指定多个列名个单个列名后的区别在于,分组的主键或者索引(indice)将一个是单个主键,另一个则是一个元组的形式:

grouped = df.groupby('key1')
grouped_muti = df.groupby([states,years])

print(grouped.size())

key1
a    3
b    2
dtype: int64

print(grouped_muti.size())

California  2005    1
            2006    1
Ohio        2005    2
            2006    1
dtype: int64

print(grouped.get_group('a'))

  key1 key2  data1  data2
0    a  one      8      6
1    a  two      6      9
4    a  one      3     -7

print(grouped_muti.get_group(('Ohio', 2005)))
  key1 key2  data1  data2
0    a  one      8      6
3    b  two      4      2

使用Series和字典作为分组

除了使用上述List类型的数据作为分组依据,还可以使用Series和字典作为分组依据。下面仅以字典类型为例:

import pandas as pd
import numpy as np
data=pd.DataFrame(np.arange(20).reshape(4,5),index=list('1234'),columns=list('12345'))

by_dict={'1':'red','2':'yellow','3':'yellow','4':'black','5':'white'}
by_dict1={'1':'red','2':'yellow','3':'yellow','5':'white'}
data_1=data.groupby(by_dict)
print("按by_dict分组的结果:")
for key,group in data_1:
    print(key)
    print(group)

data_2=data.groupby(by_dict1)
print("按by_dict1分组的结果:")
data_3=data.groupby(by_dict1)
for key,group in data_3:
    print(key)
    print(group)

注意:

使用字典或Series作为依据对数据进行分组时,如果行索引或列索引在分组依据(代码中的by_dict和by_dict1变量)中并没有找到对应关系,则对应的行或列是不参与最终的分组的(不是自成一组,可以从by_dict1分组的结果中看出此结论)。
分组依据中可以出现行索引或列索引中没有出现的值。比如by_dict1中的5
使用Series和字典时,可以设置axis参数。

grouped的函数操作

通过调用get_group()函数可以返回一个按照分组得到的DataFrame对象,所以可以将DataFrameGroupBy对象理解为是多个DataFrame组成的。
而没有调用get_group()函数之前,此时的数据结构任然是DataFrameGroupBy,此时进行对DataFrameGroupBy按照列名进行索引,
就可以得到SeriesGroupBy对象,取多个列名,则得到的任然是DataFrameGroupBy对象,这里可以类比DataFrame和Series的关系。

#A single group can be selected using get_group():
grouped.get_group("bar")
#Out: 
     A      B         C         D
1  bar    one  0.254161  1.511763
3  bar  three  0.215897 -0.990582
5  bar    two -0.077118  1.211526
Or for an object grouped on multiple columns:

#for an object grouped on multiple columns:
df.groupby(["A", "B"]).get_group(("bar", "one"))

因此,在没有进行调用get_group(),也就是没有取出特定某一组数据之前,此时的数据结构任然是DataFrameGroupBy,其中也有很多函数和方法可以调用,
如max()、count()、std()等,返回的结果是一个DataFrame对象。
调用get_group()函数后得到了Series的对象,下面的操作就可以按照Series对象中的函数行了。

print(grouped.count())
print(grouped.max()[['Age', 'Score']])
print(grouped.mean()[['Age', 'Score']])

如果其中的函数无法满足需求,也可以选择使用聚合函数aggregate,传递numpy或者自定义的函数,前提是返回一个聚合值。
关于使用自定义对数据进行分组时,要注意以下两点:

  1. 除了自定义的函数,python中的内建函数,比如len等,也可以直接用来进行分组。(此处并没有举例)
  2. 这些函数不仅可以作用在索引列上,也可以自己指定作用列。
  3. 指定了自定义函数的作用列之后,作用列中的每个值都会传入到函数中执行。并根据其函数运行结果对原始数据进行分组。

具体可参考官网的例子:https://pandas.pydata.org/pandas-docs/stable/user_guide/groupby.html

gb = df.groupby("key1")
gb.<TAB>   #(输入gb.后按Tab键,可以看到以下提示:)
gb.agg        gb.boxplot    gb.cummin     gb.describe   gb.filter     gb.get_group  gb.height     gb.last       gb.median     gb.ngroups
gb.plot       gb.rank       gb.std        gb.transform  gb.aggregate  gb.count      gb.cumprod    gb.dtype      gb.first      gb.groups 
gb.hist       gb.max        gb.min        gb.nth        gb.prod       gb.resample   gb.sum        gb.var        gb.apply      gb.cummax
gb.cumsum     gb.fillna     gb.gender     gb.head       gb.indices    gb.mean       gb.name       gb.ohlc       gb.quantile   gb.size
gb.tail       gb.weight

def getSum(data):
    total = 0
    for d in data:
        total+=d
    return total

print(grouped.aggregate(np.median))
print(grouped.aggregate({'Age':np.median, 'Score':np.sum}))
print(grouped.aggregate({'Age':getSum}))

aggregate函数不同于apply,前者是对所有的数值进行一个聚合的操作,而后者则是对每个数值进行单独的一个操作:

def addOne(data):
    return data + 1

df['Age'] = df['Age'].apply(addOne)
df['Age'] = df['Age'].apply(int)

更复杂的agg方法 pd.NamedAgg

animals = pd.DataFrame({"kind": ["cat", "dog", "cat", "dog"],
"height": [9.1, 6.0, 9.5, 34.0],
"weight": [7.9, 7.5, 9.9, 198.0]})

grouped_agg = animals.groupby("kind").agg(min_height=pd.NamedAgg(column="height", aggfunc="min"),
                                      max_height=pd.NamedAgg(column="height", aggfunc="max"),
                                      average_weight=pd.NamedAgg(column="weight", aggfunc=np.mean))

对grouped里的元素进行遍历

for name, group in grouped:
    print(name)
    print(group)

通过循环,对value进行拼接。

# 循环拼接
for key, value in data_group:
    new_data = pd.concat([new_data, value])
print(new_data)

在x,y轴上进行分组

Pandas中使用groupby时默认是在axis=0轴上进行分组的,也可以通过设置在axis=1轴上进行分组。

import pandas as pd
import numpy as np
def odd(num):
    return int(num)%2==0
data=pd.DataFrame(np.arange(20).reshape(4,5),index=list('1234'),columns=list('12345'))
print("原始数据:")
print(data)
data_axis0=data.groupby(odd,axis=0)#默认依据index在odd上的运行结果进行分组
print("按axis=0进行分组结果如下:")
for key,group in data_axis0:
    print(key)
    print(group)
data_axis1=data.groupby(odd,axis=1)#默认依据column在odd上的运行结果进行分组
print("按axis=1进行分组结果如下:")
for key,group in data_axis1:
    print(key)
    print(group)

使用groupby计算加权平均值

可以参与我的另一篇文章:https://www.cnblogs.com/treasury-manager/p/14072025.html

import numpy as np
import pandas as pd
index = pd.Index(['01/01/2012','01/01/2012','01/01/2012','01/02/2012','01/02/2012'], name='Date')
df = pd.DataFrame({'ID':[100,101,102,201,202],'wt':[.5,.75,1,.5,1],'value':[60,80,100,100,80]},index=index)
def grouped_weighted_avg(values, weights, by):
      return (values * weights).groupby(by).sum() / weights.groupby(by).sum()
cc = grouped_weighted_avg(values=df.wt, weights=df.value, by=df.index)

Out[0]: df
             ID    wt  value
Date                        
01/01/2012  100  0.50     60
01/01/2012  101  0.75     80
01/01/2012  102  1.00    100
01/02/2012  201  0.50    100
01/02/2012  202  1.00     80


Out[1]: cc
Date
01/01/2012    0.791667
01/02/2012    0.722222
dtype: float64

另外一种代码

import numpy as np
import pandas as pd

np.random.seed(234)
df= pd.DataFrame(np.random.randint(5,8,(1000,4)), columns=['a','b','c','d'])

wm = lambda x: (x * df.loc[x.index,"c"]).sum() / x.sum()
wm.__name__ = 'wa'

# 上面的公式适用于1个groupby('a')的,如果是2个groupby('a','b'),则要把公式改一下:lambda x: (x * df.loc[x.index,"c"]).sum() / df.loc[x.index,"c"]).sum()
# 否则会计算错误

f = lambda x: x.sum() / df['b'] .sum()  #这个公式看起来是计算算术平均值,而非加权平均值
f.__name__ = '%'

g = df.groupby('a').agg(
        {'b':['sum', f, 'mean', wm],
         'c':['sum','mean'],
         'd':['sum']})
g.columns = g.columns.map('_'.join)
print (g)


#wm.__name__ = 'wa'的结果:
   b_sum       b_%    b_mean  b_<lambda_0>  c_sum    c_mean  d_sum
a                                                                 
5   2067  0.344672  5.991304      5.969521   2062  5.976812   2104
6   1875  0.312656  6.009615      5.954667   1857  5.951923   1859
7   2055  0.342671  5.991254      6.085645   2084  6.075802   2058

#f.__name__ = '%'的结果
   b_sum  b_<lambda_0>    b_mean  b_<lambda_1>  c_sum    c_mean  d_sum
a                                                                     
5   2067      0.344672  5.991304      5.969521   2062  5.976812   2104
6   1875      0.312656  6.009615      5.954667   1857  5.951923   1859
7   2055      0.342671  5.991254      6.085645   2084  6.075802   2058

保留以上两句的结果:
   b_sum       b_%    b_mean      b_wa  c_sum    c_mean  d_sum
a                                                             
5   2067  0.344672  5.991304  5.969521   2062  5.976812   2104
6   1875  0.312656  6.009615  5.954667   1857  5.951923   1859
7   2055  0.342671  5.991254  6.085645   2084  6.075802   2058

分组频率计数

使用nunique或value_counts方法获取Series的唯一计数值和计数频率。

df.groupby('year')['country'].nunique()   #对df按'year'分组,并求出'country'列的唯一值的数目

案例应用

本例使用在openml.org网站上称为“ credit-g”的数据集。 该数据集由提出贷款申请的客户的许多功能和一个目标变量组成,该目标变量指示信贷是否还清。
可以在此处下载数据(https://www.openml.org/d/31),也可以使用Scikit-learn API导入数据,如下所示。

import pandas as pd 
import numpy as np 
from sklearn.datasets import fetch_openml 

X,y = fetch_openml(name='credit-g', as_frame=True, return_X_y=True) 
df = X 
df['target'] = y 
print(df)
#输出:
    checking_status  duration  ... foreign_worker target
0                <0       6.0  ...            yes   good
1          0<=X<200      48.0  ...            yes    bad
2       no checking      12.0  ...            yes   good
3                <0      42.0  ...            yes   good
4                <0      24.0  ...            yes    bad
..              ...       ...  ...            ...    ...
995     no checking      12.0  ...            yes   good
996              <0      30.0  ...            yes   good
997     no checking      12.0  ...            yes   good
998              <0      45.0  ...            yes    bad
999        0<=X<200      45.0  ...            yes   good

将所有内容按工作类型分组并计算了所有数值变量的平均值。 输出显示在代码下方。

df.groupby(['job']).mean()
Out[1]: 
                            duration  ...  num_dependents
job                                   ...                
unemp/unskilled non res    17.363636  ...        1.136364
unskilled resident         16.535000  ...        1.260000
skilled                    21.411111  ...        1.125397
high qualif/self emp/mgmt  25.168919  ...        1.141892

想要更具体一些,可以取dataframe的一个子集,只计算特定列的统计信息。在下面的代码中,只选择credit_amount。

df[['job', 'credit_amount']].groupby(['job']).mean()
Out[2]: 
                           credit_amount
job                                     
unemp/unskilled non res      2745.136364
unskilled resident           2358.520000
skilled                      3070.965079
high qualif/self emp/mgmt    5435.493243

也可以按多个变量分组。这里按工作和住房类型计算了平均信贷金额。

df[['job', 'housing','credit_amount']].groupby(['job', 'housing']).mean()
Out[3]: 
                                    credit_amount
job                       housing                
unemp/unskilled non res   rent        3110.600000
                          own         2739.538462
                          for free    2306.500000
unskilled resident        rent        2376.947368
                          own         2289.376623
                          for free    3602.000000
skilled                   rent        3107.226087
                          own         2900.807522
                          for free    4225.587302
high qualif/self emp/mgmt rent        4558.523810
                          own         5139.436170
                          for free    6836.878788

多聚合
groupby后面使用agg函数能够计算变量的多个聚合。在下面的代码计算了每个作业组的最小和最大值。

df[['job', 'credit_amount']].groupby(['job']).agg([min, max])
Out[4]: 
                          credit_amount         
                                    min      max
job                                             
unemp/unskilled non res           609.0  14555.0
unskilled resident                250.0  11998.0
skilled                           338.0  15945.0
high qualif/self emp/mgmt         629.0  18424.0

也可以对不同的列使用不同的聚合。以下计算credit_amount的最小和最大金额以及每种工作类型的平均年龄。

df[['job', 'credit_amount', 'age']].groupby(['job']).agg( 
{'credit_amount': ['min', 'max'], 'age': 'mean'})
Out[15]: 
                          credit_amount                 age
                                    min      max       mean
job                                                        
unemp/unskilled non res           609.0  14555.0  40.090909
unskilled resident                250.0  11998.0  36.540000
skilled                           338.0  15945.0  34.253968
high qualif/self emp/mgmt         629.0  18424.0  39.027027

聚合命名
NamedAgg函数允许为多个聚合提供名称,从而提供更清晰的输出。

df[['target', 'credit_amount', 'age']].groupby('target').agg( 
min_credit_amount=pd.NamedAgg('credit_amount', 'min'), 
max_credit_amount=pd.NamedAgg('credit_amount', 'max'), 
average_age=pd.NamedAgg('age', 'mean'))
Out[5]: 
        min_credit_amount  max_credit_amount  average_age
target                                                   
good                250.0            15857.0    36.224286
bad                 433.0            18424.0    33.963333

自定义聚合
也可以将自定义功能应用于groupby对聚合进行自定义的扩展。例如,如果我们要计算每种工作类型的不良贷款的百分比,我们可以使用下面的代码。

job_count = df[['job', 'target']].groupby(['job', 'target']).agg({'target': 'count'}) 
job_percent = job_count.groupby(level=0).apply(lambda x: 
100 * x / float(x.sum())) 
job_percent
Out[13]: 
                                     target
job                       target           
unemp/unskilled non res   good    68.181818
                          bad     31.818182
unskilled resident        good    72.000000
                          bad     28.000000
skilled                   good    70.476190
                          bad     29.523810
high qualif/self emp/mgmt good    65.540541
                          bad     34.459459

可视化绘图
我们可以将pandas 内置的绘图功能添加到GroupBy,以更好地可视化趋势和模式。
用上一节中创建的代码,以创建堆叠的条形图,以更好地可视化每种工作类型的好坏贷款的分布。

df.groupby(['job', 'target'])['job'].count().unstack('target').fillna(0).plot(kind='bar', stacked=True)

————————————————

参考文件:

https://blog.csdn.net/jingshuiliushen_zj/article/details/83211650
https://blog.csdn.net/m0_45210226/article/details/109406562
https://blog.csdn.net/FrankieHello/article/details/97272990
https://blog.csdn.net/brucewong0516/article/details/78768443
https://blog.csdn.net/yeshang_lady/article/details/102488971
https://blog.csdn.net/FGH333xwy/article/details/110672407

posted on 2021-03-03 15:54  chengjon  阅读(5176)  评论(0编辑  收藏  举报