文章目录
数据分析.pandas.数据的分组、聚合
对数据进行整体性的聚合运算以及分组操作也是数据分析的重要内容。
通过数据的聚合与分组,我们能更容易的发现隐藏在数据中的规律。下面我们根据星巴克的案例来进行学习~
一、分组—groupby()
这里所用到的分组方法与数据库里所学到的分组基本一样,可以按照某一列或者是某几列进行分组。
以下是groupby()函数的源码:
当然,它的底层还是有另一层源码的,这里不再深入,主要就是详细的如何分组,大家可以去看一下~但通过这上面的两端,我们不难发现groupby()方法返回的是一个Series类型的值,下面我们会解释。
下面结合案例来进行解释
基本需求:得到每个国家的星巴克店的数量
第一步:分组
import numpy as np
import pandas as pd
file_path = "starbucks/directory.csv"
df = pd.read_csv(file_path)
# print(df.head(1))
# print(df.info())
#按照国家来分类
grouped = df.groupby(by="Country")
print(grouped)
#这里的grouped得到的是一个DataFrameGroupBy对象,可迭代
二、DataFrameGroupBy对象
经过groupby()分组后得到的是一个DataFrameGroupBy对象,不可以直接显示具体数据,只能显示出在内存中的内存地址。
对于该对象具体数据,我们可以通过迭代来进行查看:
# 可以进行遍历
for i in grouped:
print(i)
print("*"*100) #返回一个两个元素的元组,第一个是国家(Country),第二个是该国家下的所有数据,类型是dataframe
# ('AE', Brand Store Number ... Longitude Latitude
# 1 Starbucks 22331-212325 ... 55.47 25.42
# 2 Starbucks 47089-256771 ... 55.47 25.39
# 3 Starbucks 22126-218024 ... 54.38 24.48
# 4 Starbucks 17127-178586 ... 54.54 24.51
# 5 Starbucks 17688-182164 ... 54.49 24.40
# .. ... ... ... ... ...
# 140 Starbucks 34253-62541 ... 55.38 25.33
# 141 Starbucks 1359-138434 ... 55.38 25.32
# 142 Starbucks 34259-54260 ... 55.37 25.30
# 143 Starbucks 34217-27108 ... 55.48 25.30
# 144 Starbucks 22697-223524 ... 55.54 25.53
# [144 rows x 13 columns])
以上是按照国家分组后每个国家的星巴克情况(由于数据较大,只截取了国家为‘AE’的,中间数据也有部分省略)
我们可以看到,该对象返回一个两个元素的元组,第一个是国家(Country),第二个是该国家下的所有数据,类型是dataframe。
三、聚合
3.1 简单直接聚合
所谓聚合,就是执行多个值变成一个值的操作。而在Python中,就是将一个矢量(数组)变成标量。在Python中主要有如图所示的几中聚合方法。
print('订单详情表分组后前5组的均值为:\n',detailgroup.mean().head())
#订单详情表分组后前5组的均值为:
counts amounts
order_id
1002 1.0000 32.000
1003 1.2500 30.125
1004 1.0625 43.875
1008 1.0000 63.000
1011 1.0000 57.700
print('订单详情表分组后前5组的标准差为:\n',detailgroup.std().head())
#订单详情表分组后前5组的标准差为:
counts amounts
order_id
1002 0.00000 16.000000
1003 0.46291 21.383822
1004 0.25000 31.195886
1008 0.00000 64.880660
1011 0.00000 50.077828
print('订单详情表分组后前5组的每组大小:\n',detailgroup.size().head())
#订单详情表分组后前5组的每组大小:
order_id
1002 7
1003 8
1004 16
1008 5
1011 10
dtype: int64
第二步:调用聚合函数计算
# 可以调用聚合函数
print(grouped.count()) #会将每一列按照国家来进行进行统计
# Brand Store Number Store Name ... Timezone Longitude Latitude
# Country ...
# AD 1 1 1 ... 1 1 1
# AE 144 144 144 ... 144 144 144
# AR 108 108 108 ... 108 108 108
# AT 18 18 18 ... 18 18 18
# AU 22 22 22 ... 22 22 22
# ... ... ... ... ... ... ...
# TT 3 3 3 ... 3 3 3
# TW 394 394 394 ... 394 394 394
# US 13608 13608 13608 ... 13608 13608 13608
# VN 25 25 25 ... 25 25 25
# ZA 3 3 3 ... 3 3 3
# [73 rows x 12 columns]
print(grouped["Brand"].count()) #也可以对其中的所需要的一列进行统计
# AD 1
# AE 144
# AR 108
# AT 18
# AU 22
# ...
# TT 3
# TW 394
# US 13608
# VN 25
# ZA 3
# Name: Brand, Length: 73, dtype: int64
3.2 使用agg方法聚合数据
agg、aggregate方法都可以对每个分组应用某个函数,也可以直接对dataframe进行函数操作。
实际操作过程中两种方法作用相同
DataFrame.agg(func,axis=0,*args,**kwargs)
DataFrame.aggregate(func,axis=0,*args,**kwargs)
可以使用agg函数求出对应的统计量;也可以根据要求,对于某个字段做单独的处理,例如对counts字段进行求和,对amounts字段进行求均值,但此时需要以字典的形式,将字段名作为key,函数作为value传入;同时还可以对不同字段的不同数目进行统计,当不唯一时只需要将字典对应的value转换为列表即可。
3.2.1 自定义函数
注意在agg中可以使用numpy库中的函数(np.mean、np.median、np.prod、np.sum、np.std、np.var),但是在自定义函数中使用此类函数时,若计算的是单个序列,则无法得到预期结果,需为多列数据同时计算。
返回顶部
3.2.2 实现对分组后的数据进行统计
3.3 使用apply方法聚合数据
与agg方法相比较,agg方法传入的参数只能够作用于整个DataFrame或者Series,并且apply方法无法像agg方法一样对不同的字段应用不同的函数来获取不同的结果。
DataFrame.apply(func,axis=0,broadcast=False,raw=False,reduce=None,arg=(),**kwds)
参数名称 | 说明 |
---|---|
func | 接收functions。代表应用于每行或每列的函数 |
axis | 接收0或1,代表操作的轴向 |
broadcast | 接收boolean,代表是否进行广播,默认为False |
raw | 接收boolean,表示是否直接将ndarray对象传递给参数,默认为False |
reduce | 接收boolean或None,表示返回的格式 |
3.4 使用transform方法聚合数据
transform方法能够对整个DataFrame的所有元素进行操作
在这里介绍一下使用lambda表达式定义函数:
1.lambda表达式定义函数是比较特殊一种函数定义方式,该函数是中匿名函数,也就是该函数没有函数名。使用该中函数会返回一个值,调用时直接使用该函数的值。
2.在lambda表达式中不能够使用print语句
3.在lambda表达式中不能够使用其他语句
groupby对象的函数
----- apply
在不同分组上应用‘func’函数,然后将结果组合起来。
----- agg/aggregate
聚合(agg/aggregate)在特定轴(列)上应用一或多个操作(函数)
----- transform
调用函数在每个分组上产生一个与原df相同索引的DataFrame,整体返回与原来对象拥有相同索引且
已填充了转换后的值的DataFrame
四、星巴克案例扩展
需求:比较中国和美国的星巴克店数
在上面的基础上,我们来获取中国和美国的星巴克店的数量
country_count = grouped["Brand"].count()
print(country_count["US"]) //美国
print(country_count["CN"]) //中国
需求:具体了解到中国的每一个省份的星巴克店的数量
china_data = df[df["Country"]=="CN"]
#注意这里在查找时是作为条件,应当为bool类型
grouped = china_data.groupby(by="State/Province").count()["Brand"]
print(grouped) #数字代表中国的省份编码
# State/Province
# 11 236
# 12 58
# 13 24
# 14 8
# 15 8
# 21 57
# 22 13
# 23 16
# 31 551
# 32 354
# 33 315
# 34 26
# 35 75
# 36 13
# 37 75
# 41 21
# 42 76
# 43 35
# 44 333
# 45 21
# 46 16
# 50 41
# 51 104
# 52 9
# 53 24
# 61 42
# 62 3
# 63 3
# 64 2
# 91 162
# 92 13
# Name: Brand, dtype: int64
需求:按照多个条件进行分组,同时按照国家和省份分组
这样会显示出每个国家的每个地区的数量情况,更加直观~
#同时按照国家和省份分组,就会有两个索引,返回的是Series
grouped = df["Brand"].groupby(by=[df["Country"],df["State/Province"]]).count()
print(grouped)
# Country State/Province
# AD 7 1
# AE AJ 2
# AZ 48
# DU 82
# FU 2
# ..
# US WV 25
# WY 23
# VN HN 6
# SG 19
# ZA GT 3
# Name: Brand, Length: 545, dtype: int64
补充:
若想返回的值是DataFrame类型,可以再案列名查找时多添加一个"[]"
grouped1 = df[["Brand"]].groupby(by=[df["Country"],df["State/Province"]]).count()
print(grouped1,type(grouped1))
print("*"*100)
grouped2 = df.groupby(by=[df["Country"],df["State/Province"]])[["Brand"]].count()
print(grouped2,type(grouped2))
print("*"*100)
grouped3 = df.groupby(by=[df["Country"],df["State/Province"]]).count()[["Brand"]]
print(grouped3,type(grouped3))
#以上的三种方式结果均为下表
# Brand
# Country State/Province
# AD 7 1
# AE AJ 2
# AZ 48
# DU 82
# FU 2
# ...
# US WV 25
# WY 23
# VN HN 6
# SG 19
# ZA GT 3
# [545 rows x 1 columns] <class 'pandas.core.frame.DataFrame'>
需求:使用matplotlib呈现出店铺总数排名前十的国家
#使用matplotlib呈现出店铺总数排名前十的国家
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from matplotlib import font_manager
#设置字体
#设置字体
my_font=font_manager.FontProperties(fname="C:\Windows\Fonts\STXINGKA.TTF",size=18)
#准备数据
file_path = "starbucks/directory.csv"
df = pd.read_csv(file_path)
data = df.groupby(by="Country").count()["Brand"].sort_values(ascending=False)[:10]
x = data.index
y = data.values
#设置图形大小
plt.figure(figsize=(20,8),dpi=80)
#绘图
plt.bar(range(len(x)),y)
plt.xticks(range(len(x)),x)
#添加信息
plt.xlabel("国家",fontproperties=my_font)
plt.ylabel("该国星巴克店铺数",fontproperties=my_font)
plt.title("星巴克店铺总数排名前十的国家",fontproperties=my_font)
#展示
plt.show()
需求:中国星巴克数排名前25的城市店铺总数
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from matplotlib import font_manager
#设置字体
#设置字体
my_font=font_manager.FontProperties(fname="C:\Windows\Fonts\STXINGKA.TTF",size=18)
#准备数据
file_path = "starbucks/directory.csv"
df = pd.read_csv(file_path)
df = df[df["Country"]=="CN"]
data = df.groupby(by="City").count()["Brand"].sort_values(ascending=False)[:25]
x = data.index
y = data.values
#设置图形大小
plt.figure(figsize=(20,8),dpi=80)
#绘图
plt.bar(range(len(x)),y,width=0.3)
plt.xticks(range(len(x)),x,fontproperties=my_font)
#添加信息
plt.xlabel("城市",fontproperties=my_font)
plt.ylabel("该城市星巴克店铺数",fontproperties=my_font)
plt.title("中国星巴数排名前25的城市克店铺总",fontproperties=my_font)
#绘制网格
plt.grid()
#展示
plt.show()
通过绘制图形显示直接明了,但是这里还有不足之处,就是x轴太密集,不便于观察,接下来我们进行操作,使用横向的条形图。(关于条形图的绘制可参考本人之前的文章)
x = data.index
y = data.values
#设置图形大小
plt.figure(figsize=(20,8),dpi=80)
#绘图
plt.barh(range(len(x)),y,height=0.3)
plt.yticks(range(len(x)),x,fontproperties=my_font)
#添加信息
plt.ylabel("城市",fontproperties=my_font)
plt.xlabel("该城市星巴克店铺数",fontproperties=my_font)
plt.title("中国星巴数排名前25的城市克店铺总数",fontproperties=my_font)
五、911紧急拨号案例扩展
需求:计算911紧急拨号不同类型的次数
根据已经获取到的数据,我们可以从中发现,具体的分类是在title列下字符串的前半段。至此,我们可以明确目标:
1.获取分号之前的字符串 ----- 按照“:”切割
2.统计 ----- sum()、count()
方法一:在原有数据基础上,直接分类统计
思路:首先,按照“:”分割字符串并将结果转化为list;接着,遍历list,将第i个元素中的第一个子元素取出,就得到了每个元素的分类,再用set()去重,最后转为list得到所有分类类型的列表;然后,我们就该统计,按照常理,既然有了所有分类,之前又得到了分割后具有分类的列表,那么直接去遍历,对应分类计数即可。在执行过程中会有两种方式:
1.逐行遍历赋值
每一行都去和分类比较,在对应的分类下计数
这种方式理论可行,但在实际的操作执行过程中很耗时间。
2.分类赋值
按照找出的分类,按照分类赋值
其中涉及contains()函数,它可以迅速返回目标的布尔值,借助布尔值实现直接分类赋值。
在统计时我们还要创建一组全为零的dataframe,遍历计数是需要用到的。
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from matplotlib import font_manager
#设置中文
my_font = font_manager.FontProperties(fname="C:\Windows\Fonts\STXINGKA.TTF",size=18)
#读取csv文件
file_path = "911-calls/911.csv"
df = pd.read_csv(file_path)
#获取分类
temp_list = df["title"].str.split(":").tolist()
#分类在title字段中,这里将以:切分字符串并转化为list,每个元素类似于['EMS', ' SUBJECT IN PAIN']
cate_list = list(set(i[0] for i in temp_list))
#先遍历list,将第i个元素中的第一个子元素取出,就得到了每个元素的分类,再用set()去重,最后转为list得到分类的列表
print(cate_list) #['Traffic', 'Fire', 'EMS']
#构造全为0的数组
zeros_df = pd.DataFrame(np.zeros((df.shape[0],len(cate_list))),columns=cate_list)
#赋值
#法一,遍历分类,为该分类的赋值
for cate in cate_list:
zeros_df[cate][df["title"].str.contains(cate)] = 1
print(zeros_df)
#法二,逐行遍历赋值
# for i in range(df.shape[0]):
# zeros_df.loc[i,temp_list[i][0]] = 1
# print(zeros_df)
sum_ret = zeros_df.sum(axis=0)
print(sum_ret)
方法二:在原有数据基础上,再设置一列为分类,进行对该列的统计
思路:首先,按照“:”分割字符串并将结果转化为list;接着,遍历list,将第i个元素中的第一个子元素取出,定义为新的一列;最后,直接分组统计分类一列即可。
import numpy as np
import pandas as pd
from matplotlib import pyplot as plt
from matplotlib import font_manager
#设置中文
my_font = font_manager.FontProperties(fname="C:\Windows\Fonts\STXINGKA.TTF",size=18)
#读取csv文件
file_path = "911-calls/911.csv"
df = pd.read_csv(file_path)
#获取分类
temp_list = df["title"].str.split(":").tolist()
#分类在title字段中,这里将以:切分字符串并转化为list,每个元素类似于['EMS', ' SUBJECT IN PAIN']
cate_list = [i[0] for i in temp_list]
#先遍历list,获取出第i个元素中的第一个子元素
cate_df = pd.DataFrame(np.array(cate_list).reshape((df.shape[0]),1))
df["cate"] = cate_df #添加新的一列分类
print(df.groupby(by="cate").count()["title"])