本来是准备直接上实战的,后来想了想还是先把必要的东西讲完吧,要不然到时候实战很多东西会很懵,又要递归的去讲就很麻烦。
这篇文章就是pandas的最后一篇内容讲解了,三篇连到一块应该足够应对绝大多数的内容过了,实际上也把pandas的主要内容讲的差不多了,漂亮的结一个尾吧。
先来口头阐述一下分组的概念
分组与聚合
其实分组就是字面意思呀
比如如下数据:(代码导入数据)
import pandas as pd
file_path = './split_test.xlsx'
df = pd.read_excel(file_path,sheet_name='Sheet1')
df
part | id | name | score | |
---|---|---|---|---|
0 | a | 1 | 张三 | 10 |
1 | a | 2 | 李四 | 5 |
2 | b | 3 | 王五 | 10 |
3 | b | 4 | smith | 6 |
4 | c | 5 | kear | 7 |
5 | c | 6 | clack | 8 |
将,part为a的放到一块,part为b的放到一块,part为c的放到一块就是一个分组。这种操作在sql语句中以gruoupby执行,实际上在pandas中也是这个方法,如下:
df1 = df.groupby('part')
输出数据
df1
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x0000024543471490>
可以看到它是获取了一个dataframegroupby对象的。
# 尝试读取它的数据,这一步没有出错说明它是可以迭代的。
for line in df1:
print(line)
('a', part id name score
0 a 1 张三 10
1 a 2 李四 5)
('b', part id name score
2 b 3 王五 10
3 b 4 smith 6)
('c', part id name score
4 c 5 kear 7
5 c 6 clack 8)
# print([line for line in df1])
print(list(df1)[0])
('a', part id name score
0 a 1 张三 10
1 a 2 李四 5)
第一次遍历可以看到一个元组内两个元素,尝试利用两个占位符去遍历它。
for part,ele in df1:
print(part,ele)
a part id name score
0 a 1 张三 10
1 a 2 李四 5
b part id name score
2 b 3 王五 10
3 b 4 smith 6
c part id name score
4 c 5 kear 7
5 c 6 clack 8
# 查看一个块的类型
type(ele)
pandas.core.frame.DataFrame
这说明,每一组的ele也就是元素,都是一个dataframe对象,那么它自然而然继承了dataframe的所有方法。自然也可以取出其中所有的元素。
前面给一个印象呀。接下来我们来具体说明方法
# 查看数据
df
part | id | name | score | |
---|---|---|---|---|
0 | a | 1 | 张三 | 10 |
1 | a | 2 | 李四 | 5 |
2 | b | 3 | 王五 | 10 |
3 | b | 4 | smith | 6 |
4 | c | 5 | kear | 7 |
5 | c | 6 | clack | 8 |
# 整体对某个键进行分组
df_group_obj = df.groupby('part')
# 查看数据,可以发现,是为一个对象,注意这个对象仅包含分组的中间数据
print(df_group_obj)
# 利用该对象的size属性可以查看对应的元素数量
print(df_group_obj.size())
# 该对象同样可以调用某些dataframe对象的方法,比如:
# 对分组每列求均值
print(df_group_obj.mean())
# 对分组每列求和
print(df_group_obj.sum())
# 取出分组每列的最大值
print(df_group_obj.max())
# 取出分组每列的最小值
print(df_group_obj.min())
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x00000245494B2520>
part
a 2
b 2
c 2
dtype: int64
id score
part
a 1.5 7.5
b 3.5 8.0
c 5.5 7.5
id score
part
a 3 15
b 7 16
c 11 15
id name score
part
a 2 李四 10
b 4 王五 10
c 6 kear 8
id name score
part
a 1 张三 5
b 3 smith 6
c 5 clack 7
还要注意以上操作都是去对分组进行操作的,所以如果分为三个组,那么结果应该是三条记录(也就是三行)
上述操作是对整个df对象进行分组,同样可以取出每一列进行分组。
也就是对一个series对象同样可以进行该运算
# 但这里要注意groupby中的参数一定要用df取出,否则会报错,其实不难想呀,因为df['part']本身就是一个series对象,将两个series对象拼接起来,就是一
# 个dataframe,因此这里才可以使用分组方法。
ser_group_by = df['score'].groupby(df['part'])
# 剩下的操作跟上述相同,就需要过多解释了。
print(ser_group_by.size())
print(ser_group_by.max())
print(ser_group_by.min())
part
a 2
b 2
c 2
Name: score, dtype: int64
part
a 10
b 10
c 8
Name: score, dtype: int64
part
a 5
b 6
c 7
Name: score, dtype: int64
同样,我们可以自定义分组,只需要,定义的序列元素数与记录数(行数)是一样的。
# 随机产生一个序列
import random
group = [random.randint(1,4) for i in range(6)]
# 分组
df_group = df.groupby(group)
# 查看数据
print(df_group)
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x000002454927ABB0>
print(df_group.size())
1 3
3 2
4 1
dtype: int64
## 作为验证,可以打印序列看看
print(group)
[1, 4, 1, 1, 3, 3]
根据多个键进行分组,构成分层索引
df_group = df.groupby(['part', 'name'])
print(df_group.size())
part name
a 张三 1
李四 1
b smith 1
王五 1
c clack 1
kear 1
dtype: int64
另外我们需要考虑分组产生的数据的性质,根据上面的数据来分析
- 它是一个独特的对象,一个中间数据,且仅包含数据,该数据可以进行任何对于series和dataframe对象的运算。
- 它的索引由我们规定。比如我们分组是采用的两个键为参数,那么如上所示,它包含两层索引。
- 这个对象支持我们使用pandas的某些方法进行运算,但不一定支持所有的方法,所以在实在无法处理的情况下,我们可以将该对象转化为常用的数据结构,比如,dict或者list。
- 该对象支持迭代操作。
# 转化为列表
list(df_group)
[(('a', '张三'),
part id name score
0 a 1 张三 10),
(('a', '李四'),
part id name score
1 a 2 李四 5),
(('b', 'smith'),
part id name score
3 b 4 smith 6),
(('b', '王五'),
part id name score
2 b 3 王五 10),
(('c', 'clack'),
part id name score
5 c 6 clack 8),
(('c', 'kear'),
part id name score
4 c 5 kear 7)]
# 转化为字典
dict(list(df_group))
{('a',
'张三'): part id name score
0 a 1 张三 10,
('a',
'李四'): part id name score
1 a 2 李四 5,
('b',
'smith'): part id name score
3 b 4 smith 6,
('b',
'王五'): part id name score
2 b 3 王五 10,
('c',
'clack'): part id name score
5 c 6 clack 8,
('c',
'kear'): part id name score
4 c 5 kear 7}
聚合运算
其实,在我们前面所说的内容中,已经包含了聚合运算,说白了它就是对每个分组的几条记录进行运算,不论是max,min,mean,sum,size方法都属于聚合运算。
前面没说的有一个describe方法,这个方法在讲dataframe的时候,已经讲过了,而且其实前文明确说出了,对于分组对象来说,dataframe的方法基本都是适用的呀,所以就没有特别提出。
df_group.describe()
id | score | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
count | mean | std | min | 25% | 50% | 75% | max | count | mean | std | min | 25% | 50% | 75% | max | ||
part | name | ||||||||||||||||
a | 张三 | 1.0 | 1.0 | NaN | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 1.0 | 10.0 | NaN | 10.0 | 10.0 | 10.0 | 10.0 | 10.0 |
李四 | 1.0 | 2.0 | NaN | 2.0 | 2.0 | 2.0 | 2.0 | 2.0 | 1.0 | 5.0 | NaN | 5.0 | 5.0 | 5.0 | 5.0 | 5.0 | |
b | smith | 1.0 | 4.0 | NaN | 4.0 | 4.0 | 4.0 | 4.0 | 4.0 | 1.0 | 6.0 | NaN | 6.0 | 6.0 | 6.0 | 6.0 | 6.0 |
王五 | 1.0 | 3.0 | NaN | 3.0 | 3.0 | 3.0 | 3.0 | 3.0 | 1.0 | 10.0 | NaN | 10.0 | 10.0 | 10.0 | 10.0 | 10.0 | |
c | clack | 1.0 | 6.0 | NaN | 6.0 | 6.0 | 6.0 | 6.0 | 6.0 | 1.0 | 8.0 | NaN | 8.0 | 8.0 | 8.0 | 8.0 | 8.0 |
kear | 1.0 | 5.0 | NaN | 5.0 | 5.0 | 5.0 | 5.0 | 5.0 | 1.0 | 7.0 | NaN | 7.0 | 7.0 | 7.0 | 7.0 | 7.0 |
重点要点出的方法,自定义聚合方法
def my_func(df):
"""
"""
return df.max() - df.min()
df_group_obj.agg(my_func)
# 函数传参,如果我们的函数带有()的话,这说明我们传的是函数的返回值,而不是一个方法,所以进行函数传参的的时候是不能带有()的。
id | score | |
---|---|---|
part | ||
a | 1 | 5 |
b | 1 | 4 |
c | 1 | 1 |
type(df_group_obj.agg(my_func))
pandas.core.frame.DataFrame
# 通过type可以看到,聚合方法返回的也是dataframe呀,我们可以通过agg传入多个函数名,得到结果如下:
df_group_obj.agg(['sum',my_func])
id | score | |||
---|---|---|---|---|
sum | my_func | sum | my_func | |
part | ||||
a | 3 | 1 | 15 | 5 |
b | 7 | 1 | 16 | 4 |
c | 11 | 1 | 15 | 1 |
# 它的可操作性非常强呀,包括我们可以直接改变列名。
df_group_obj.agg(['sum',('新的列名',my_func)],)
id | score | |||
---|---|---|---|---|
sum | 新的列名 | sum | 新的列名 | |
part | ||||
a | 3 | 1 | 15 | 5 |
b | 7 | 1 | 16 | 4 |
c | 11 | 1 | 15 | 1 |
这里停一下,如上算是一个分组聚合的基本内容,上面的东西吃透了,基本就过关。
下来我们来看相对与基础来说,算进阶的内容,上一个模拟实例
通过上面的方法包括前面的一二篇博客,显然我们是已经能做到excel上的很多功能操作了,但是pandas的强大远不止于此。它能体现出代替excel的趋势,其实是依靠在这个行业非常顶级的一些python数据分析师,包括我们所有人都是想办法往这些方向靠拢啊,作为学习代价呢,excel显然是比python低的,并不是有什么歧视呀,这应该算公知,从定义来看,excel作为一款成熟的软件,pandas作为一个编程语言的工具包,它更底层呀,而更底层的东西,要实现同样的功能往往更难呀,但是如果你熟练了,你能做的,也会比单用excel要强大很多。
import pandas as pd
import random as rd
import string
## 随机生成一系列成熟数据,并写入excel表格。
data = {
'id':[i for i in range(30)],
'name': [''.join([rd.choice(string.ascii_letters) for i in range(5)]) for i in range(30)], # 姓名是可重复的。
'part': [rd.choice(['a','b','c','d']) for i in range(30)],
'score': [rd.randint(60,100) for i in range(30)]
}
data = pd.DataFrame(data)
data
data.to_excel('fin_exam.xlsx', sheet_name='data_score')
id | name | part | score | |
---|---|---|---|---|
0 | 0 | DrPYm | a | 69 |
1 | 1 | JFOUg | d | 89 |
2 | 2 | fDGRR | a | 94 |
3 | 3 | QEaJz | a | 66 |
4 | 4 | IQwCs | a | 89 |
5 | 5 | QglNK | a | 98 |
6 | 6 | YEHEx | b | 77 |
7 | 7 | CThQU | a | 61 |
8 | 8 | ypFKY | a | 67 |
9 | 9 | YkzKn | b | 72 |
10 | 10 | BgoYQ | b | 75 |
11 | 11 | WLSAQ | a | 91 |
12 | 12 | wvGtX | c | 87 |
13 | 13 | wZigu | a | 64 |
14 | 14 | CgNab | d | 69 |
15 | 15 | OLwjx | c | 98 |
16 | 16 | OhMaH | d | 75 |
17 | 17 | wZXNp | b | 82 |
18 | 18 | unlzR | a | 93 |
19 | 19 | mHbyE | b | 69 |
20 | 20 | VjgMR | a | 63 |
21 | 21 | JkrpE | c | 89 |
22 | 22 | QLetK | a | 84 |
23 | 23 | MIcMu | d | 63 |
24 | 24 | DYdgN | c | 63 |
25 | 25 | rrjMV | d | 98 |
26 | 26 | mzSvn | c | 75 |
27 | 27 | BjWdT | d | 76 |
28 | 28 | dEPBF | d | 88 |
29 | 29 | lrsGq | c | 62 |
任务:取出每一组的前三人的成绩
# 分组
data_group = data.groupby('part')
# 查看数据数量
print(data_group.size())
# 自定义运算
def top_3(df):
# ascending=False参数表示降序排序
return df.sort_values(by='score', ascending=False)[:3]
# 结果
data_group.apply(top_3)
part
a 12
b 5
c 6
d 7
dtype: int64
id | name | part | score | ||
---|---|---|---|---|---|
part | |||||
a | 5 | 5 | QglNK | a | 98 |
2 | 2 | fDGRR | a | 94 | |
18 | 18 | unlzR | a | 93 | |
b | 17 | 17 | wZXNp | b | 82 |
6 | 6 | YEHEx | b | 77 | |
10 | 10 | BgoYQ | b | 75 | |
c | 15 | 15 | OLwjx | c | 98 |
21 | 21 | JkrpE | c | 89 | |
12 | 12 | wvGtX | c | 87 | |
d | 25 | 25 | rrjMV | d | 98 |
1 | 1 | JFOUg | d | 89 | |
28 | 28 | dEPBF | d | 88 |
任务:取出每一组的倒数三人的成绩
def top_d3(df):
# ascending=False参数表示降序排序
return df.sort_values(by='score', ascending=False)[-3:]
data_group.apply(top_d3)
id | name | part | score | ||
---|---|---|---|---|---|
part | |||||
a | 13 | 13 | wZigu | a | 64 |
20 | 20 | VjgMR | a | 63 | |
7 | 7 | CThQU | a | 61 | |
b | 10 | 10 | BgoYQ | b | 75 |
9 | 9 | YkzKn | b | 72 | |
19 | 19 | mHbyE | b | 69 | |
c | 26 | 26 | mzSvn | c | 75 |
24 | 24 | DYdgN | c | 63 | |
29 | 29 | lrsGq | c | 62 | |
d | 16 | 16 | OhMaH | d | 75 |
14 | 14 | CgNab | d | 69 | |
23 | 23 | MIcMu | d | 63 |
如上其实就是用了一个apply方法:它可以使用我们自定义的方法。
那么同样可代替的方法还有:
- transform:传入自定义方法(这里就不多讲了,跟apply是一样的,没意义了)