利用Python进行数据分析——数据聚合与组操作

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_31823267/article/details/79980299
import numpy as np
import pandas as pd

GroupBy机制


上图显示了一个分组背后的具体操作,当操作一个数据集按照某个key进行分组时,数据集首先会按组进行分割,然后再对每一组应用函数,最后返回分组后的结果。

df=pd.DataFrame({
    'key1':['a','a','b','b','a'],
    'key2':['one','two','one','two','one'],
    'data1':np.random.randn(5),
    'data2':np.random.randn(5)
})
df.loc[:,'data1'].groupby(df.loc[:,'key1']).mean()        #将data1列按照key1进行分组,再求均值

当需要按照多个key进行分组时,给groupby()传递一个列表即可,得到的结果是具有层级index的Series:

mean=df.loc[:,'data1'].groupby([df.loc[:,'key1'],df.loc[:,'key2']]).mean()
mean.unstack()

当对整个数据集进行分组时,可以直接给groupby()传递key的值,此时不可再用loc()与iloc()方法,因为groupby()生成的是一个groupby对象,而不是DataFrame:

df.groupby(['key1','key2'])['data2'].mean()

另一个应用于groupby()之后的方法为size():

df.groupby(['key1','key2']).size()

groups中的迭代

GroupBy对象是一个可迭代对象:

for key,data in df.groupby('key1'):
    print(key)
    print(data)

根据Dicts与Series分组

建立映射关系如dict或series,将列先进行映射再进行分组。

people=pd.DataFrame(
    np.random.randn(5,5),
    index=['Joe','Steve','Wes','Jim','Travis'],
    columns=['a','b','c','d','e']
)
people.iloc[2,[1,2]]=None

mapping={
    'a':'red',
    'b':'red',
    'c':'blue',
    'd':'blue',
    'e':'red',
    'f':'orange'
}
people.groupby(mapping,axis=1).mean()

map_series=pd.Series(mapping)
people.groupby(map_series,axis=1).mean()

应用函数进行分组

比如对上述数据集,如需要对名字长度进行分组,则可以给groupby()传递一个函数作为参数:

people.groupby(len,axis=0).mean()

根据索引等级分组

对于带层级索引的数据集,可以根据某一层级的索引名称进行分组,指定参数level=即可:

hier_df=pd.DataFrame(
    np.random.randn(4,5),
    columns=[['US','US','US','JP','JP'],
             [1,3,5,1,3]]
)
hier_df.columns.names=['city','tensor']

hier_df.groupby(level='city',axis=1).count()

数据聚合

tips=pd.read_csv('examples/tips.csv')
tips.loc[:,'tip_pct']=tips.loc[:,'tip']/tips.loc[:,'total_bill']
tips.sample(3)

grouped=tips.groupby(['day','smoker'])
grouped['tip_pct'].mean()

可以对groupby对象的agg()方法传递一个函数名列表,会返回一个groupby对象应用函数后生成的DataFrame:

grouped.agg(['min','max','mean'])

返回无行索引的聚合数据

使用参数as_index=参数来返回一个无行索引的数据:

tips.groupby(['day','smoker'],as_index=False).mean()


可见行索引被转换成了列值。

Apply

GroupBy()方法最普遍的目的就是在数据集上应用函数以筛选出想要的数据。比如用户自己写了一个top()函数,它会返回DataFrame指定列中值最大的n条记录:

def top(df,n=5,column='tip_pct'):
    return df.sort_values(by=column).iloc[-n:,:]
tips.groupby('smoker').apply(top,n=3)        #筛选3条tip_pct最大的数据

可以看到以’smoker’分组方法筛选出来的数据条目中还包含了由分组产生的’smoker’行索引,可以使用参数group_keys=来消除分组索引:

tips.groupby('smoker',group_keys=False).apply(top,n=3)        #筛选3条tip_pct最大的数据

分位数与桶分析

frame=pd.DataFrame({
    'data1':np.random.randn(1000),
    'data2':np.random.randn(1000)
})
quartiles=pd.cut(frame.loc[:,'data1'],4)        #以data1生成四个区间
def get_stats(group):
    return {
        'min':group.min(),
        'max':group.max(),
        'mean':group.mean()
    }
grouped=frame.loc[:,'data2'].groupby(quartiles)        #以data1的区间来对data2进行分组
grouped.apply(get_stats).unstack()

示例

使用分组产生的值来填充缺失值

(待补充)

随机采样与改序

假设需要生成一组随机采样(有放回或无放回)来做蒙特卡洛分析,或者是用做机器学习项目中的数据子集,需要对原数据集进行随机采样或者改序。

#生成一副牌组Series
suits=['H','S','C','D']
base_name=['A']+list(range(2,11))+['J','Q','K']
cards=[]

for suit in suits:
    cards.extend(str(num)+suit for num in base_name)

card_val=(list(range(1,14)))*4
deck=pd.Series(card_val,index=cards)
#抽牌函数
def draw(deck,n=5):
    return deck.sample(n)
draw(deck)

4H 4
6C 6
5C 5
AH 1
7C 7
dtype: int64

假设现在需要从每个花色中随机抽出两张牌,可以先按花色来对牌组进行分组再应用抽牌函数:

get_suit=lambda card:card[-1]        #card的最后一位表示花色,这里没太懂
deck.groupby(get_suit,group_keys=False).apply(draw,n=2)

6C 6
10C 10
KD 13
QD 12
9H 9
JH 11
5S 5
6S 6
dtype: int64

分组的加权均值与相关系数

如果数据是带权重的,使用apply()方法可以很方便的计算加权平均值:

frame=pd.DataFrame({
    'category':['a','a','b','b'],
    'data':np.random.randn(4),
    'weights':np.random.rand(4)
})
frame

get_wavg=lambda grouped:np.average(grouped.loc[:,'data'],weights=grouped.loc[:,'weights'])
frame.groupby('category').apply(get_wavg)

在下一个例子前,先了解一下pct_change()方法:

obj=pd.Series(np.arange(1,5))
obj.pct_change()

接下来我们导入一个真实数据集,进行相关性分析:

close_px=pd.read_csv('examples/stock_px_2.csv',parse_dates=True,index_col=0)
close_px.info()

close_px.head()

使用pct_change()来计算日回报率:

returns=close_px.pct_change().dropna()
returns.head()

我们把数据按年进行聚合,再使用apply()方法显示以SPX为参照的各公司收益相关性:

get_year=lambda timestamp:timestamp.year        #此数据集的行索引为时间对象,无loc()方法
spx_cor=lambda x:x.corrwith(x.loc[:,'SPX'])
returns.groupby(get_year).apply(spx_cor)

还可以得到指定两家公司的年收益相关性:

AAPL_cor_MSFT=lambda x:x.loc[:,'AAPL'].corr(x.loc[:,'MSFT'])
returns.groupby(get_year).apply(AAPL_cor_MSFT)

2003 0.480868
2004 0.259024
2005 0.300093
2006 0.161735
2007 0.417738
2008 0.611901
2009 0.432738
2010 0.571946
2011 0.581987
dtype: float64

透视表与交叉表

pivot_table()方法与groupby()方法能得到相似的结果,不过pivot_table()方法能提供更强大的功能。

tips=pd.read_csv('examples/tips.csv')
tips.pivot_table(index=['day','smoker'])

以上结果同样可以通过tips.groupby(['day','smoker'])得到。如果现在我们只想生成tip和size列的透视表,以time和day来分组:

tips.pivot_table(['tip','size'],index=['time','day'],columns='smoker')

交叉表

以两个Series或list组成一个表

pd.crosstab([tips.loc[:,'time'],tips.loc[:,'day']],tips.loc[:,'smoker'],margins=True)        #margins显示总额

阅读更多
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页