Pandas数据分析
一、【了解】Pandas框架概述
Pandas介绍
Python在数据处理上独步天下:代码灵活、开发快速;尤其是Python的Pandas包,无论是在数据分析领域、还是大数据开发场景中都具有显著的优势:
- Pandas是Python的一个第三方包,也是商业和工程领域最流行的结构化数据工具集,用于数据清洗、处理以及分析
- Pandas在数据处理上具有独特的优势:
- 底层是基于Numpy构建的,所以运行速度特别的快
- 有专门的处理缺失数据的API
- 强大而灵活的分组、聚合、转换功能

适用场景:
- 数据量大到Excel严重卡顿,且又都是单机数据的时候,我们使用Pandas
- Pandas用于处理单机数据(小数据集(相对于大数据来说))
- 在大数据ETL数据仓库中,对数据进行清洗及处理的环节使用Pandas
安装Pandas
注意:Anaconda默认已经安装了Pandas以及Numpy等内容
打开cmd窗口,输入如下命令:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple/ pandas

【演示】Pandas初体验
- 1- 将资料中提供的数据集导入到data目录中

- 2- 创建python脚本, 导入pandas库
import pandas as pd
- 3- 基于pandas加载数据
df = pd.read_csv('data/1960-2019全球GDP数据.csv', encoding='gbk')
# 设置显示的最大行数和列数为None
pd.set_option(‘display.max_rows’, None)
pd.set_option(‘display.max_columns’, None)# 恢复显示的最大行数到默认值
pd.reset_option(‘display.max_rows’)# 恢复显示的最大列数到默认值
pd.reset_option(‘display.max_columns’)# 恢复所有选项到默认值
pd.reset_option(‘all’)
- 4- 基于pandas完成相关查询:
# 查询中国的GDP
china_gdp = df[df.country=='中国'] # df.country 选中名为country的列
china_gdp.head(10) # 显示前10条数据
运行结果:

- 5- 将year年份设置为索引
china_gdp = china_gdp.set_index('year')
china_gdp.head() # 默认显示前5条

- 6- 画出GDP逐年变化的曲线图
china_gdp.GDP.plot()

使用同样的方法画出日本的GDP变化曲线,和中国的GDP变化曲线进行对比
jp_gdp = df[df.country=='日本'].set_index('year') # 按条件选取数据后,重设索引
jp_gdp.GDP.plot()
china_gdp.GDP.plot()

分别查询中国、美国、日本三国的GDP数据,并绘制GDP变化曲线、进行对比
china_gdp = df[df.country=='中国'].set_index('year')
us_gdp = df[df.country=='美国'].set_index('year')
jp_gdp = df[df.country=='日本'].set_index('year')
jp_gdp.GDP.plot()
china_gdp.GDP.plot()
us_gdp.GDP.plot()

设置图例:
# 按条件选取数据
china_gdp = df[df.country=='中国'].set_index('year')
us_gdp = df[df.country=='美国'].set_index('year')
jp_gdp = df[df.country=='日本'].set_index('year')
# 出图并添加图例
jp_gdp.GDP.plot(legend=True)
china_gdp.GDP.plot(legend=True)
us_gdp.GDP.plot(legend=True)

修改列名使图例显示为各国名称
# 按条件选取数据
china_gdp = df[df.country=='中国'].set_index('year')
us_gdp = df[df.country=='美国'].set_index('year')
jp_gdp = df[df.country=='日本'].set_index('year')
# 对指定的列修改列名
jp_gdp.rename(columns={'GDP':'japan'}, inplace=True)
china_gdp.rename(columns={'GDP':'china'}, inplace=True)
us_gdp.rename(columns={'GDP':'usa'}, inplace=True)
# 画图
jp_gdp.japan.plot(legend=True)
china_gdp.china.plot(legend=True)
us_gdp.usa.plot(legend=True)

- 7- 解决中文不能在图表中正常显示的问题
将列名改为中文,使图例显示为各国名称
# 按条件选取数据
china_gdp = df[df.country=='中国'].set_index('year')
us_gdp = df[df.country=='美国'].set_index('year')
jp_gdp = df[df.country=='日本'].set_index('year')
# 对指定的列修改列名
jp_gdp.rename(columns={'GDP':'日本'}, inplace=True)
china_gdp.rename(columns={'GDP':'中国'}, inplace=True)
us_gdp.rename(columns={'GDP':'美国'}, inplace=True)
# 画图
jp_gdp['日本'].plot(legend=True)
china_gdp['中国'].plot(legend=True)
us_gdp['美国'].plot(legend=True)
# 解决中文显示问题,下面的代码只需运行一次即可
import matplotlib as plt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
运行结果:

小结
Python Pandas的作用:(清洗、处理、分析数据)
Pandas环境搭建:
- 安装Anaconda,默认自带Python以及其他相关三方包
- 使用默认的base虚拟环境启动
JupyterNotebook
二、【熟悉】Pandas数据结构与数据类型
掌握内容
1- 创建
pd.DataFrame(ndarray)
2- 方法
head()
tail()
3- 属性
values
T
4- 索引
set_index()
5- 数据类型
object、int、float、nan
info()
dtypes
Pandas数据结构和数据类型

上图为上一节中读取并展示出来的数据,以此为例我们来讲解Pandas的核心概念,以及这些概念的层级关系:
- DataFrame
- Series
- 数据列
- 列名
- 列值,具体的数据
- 数据列
- 索引列
- 索引名、索引值
- 索引下标、行号
- Series
其中最核心的就是Pandas中的两个数据结构:DataFrame和Series
Series对象
Series也是Pandas中的最基本的数据结构对象,下文中简称s对象,是DataFrame的列对象,series本身也具有索引。
Series是一种类似于一维数组的对象,由下面两个部分组成:

- values:一组数据(numpy.ndarray类型)
- index:相关的数据索引标签;如果没有为数据指定索引,于是会自动创建一个0到N-1(N为数据的长度)的整数型索引。
创建Series对象
- 1- 导入pandas
import pandas as pd
import numpy as np
- 2- 通过list列表来创建
# 使用默认自增索引
s2 = pd.Series([1, 2, 3])
print(s2)
# 使用自己指定的索引
# 注意:索引个数与值的个数必须完全一致
s3 = pd.Series(data=[11,22,33], index=["a","b","c"])
# s3 = pd.Series(data=[11,22,33], index=["a","b","c","d"])
# s3 = pd.Series(data=[11,22,33], index=["a","b"])
print(s3)
- 3- 使用字典或元组创建series对象
#使用元组
tst = (1,2,3,4,5,6)
pd.Series(tst)
#使用字典:
dst = {'A':1,'B':2,'C':3,'D':4,'E':5,'F':6}
pd.Series(dst)
- 4- 使用numpy创建series对象
pd.Series(np.arange(10))
# 运行结果
0 0
1 1
2 2
3 3
4 4
5 5
6 6
7 7
8 8
9 9
dtype: int64
课堂演示完整代码:
import pandas as pd
# 1----------- Series对象
# 1.1- 创建
# 通过list列表来创建
# 查看源代码:Ctrl+Q Ctrl+鼠标左键
s1 = pd.Series(data=[11,22,33])
s1
# 使用自己指定的索引
# 注意:索引个数与值的个数必须完全一致
s2 = pd.Series(data=[11,22,33], index=["a","b","c"])
# s2 = pd.Series(data=[11,22,33], index=["a","b","c","d"])
# s2 = pd.Series(data=[11,22,33], index=["a","b"])
s2
# 通过元组或者字典来创建
s3 = pd.Series((11,22,33))
s3
my_dict = {"a":111,"b":6666,"c":6666}
# 字典中的key会作为行索引来使用;value作为字段值使用
s4 = pd.Series(my_dict)
s4
# 通过Numpy来创建
import numpy as np
arr = np.arange(start=1,stop=10,step=1) # 左闭右开的区间
print(arr)
s5 = pd.Series(arr)
s5
Series对象属性
构造一个series对象
s6 = pd.Series(data=[i*10 for i in range(5)], index=[i for i in "ABCDE"])
s6
# 返回结果如下
A 0
B 10
C 20
D 30
E 40
dtype: int64
- 1- series对象常用属性
s6.index
- 2- values
s6.values
- 3- 也可以通过索引来获取数据
s6['C']
课堂演示完整代码:
import pandas as pd
# Series对象的属性
s6 = pd.Series(data=[i*10 for i in range(5)], index=[i for i in "ABCDE"])
print(f"Series的全部内容:\n{s6}")
print(f"获得索引:{s6.index}")
print(f"获得数据值:{s6.values}")
print(f"通过index索引获得数据值:{s6['C']}")
DataFrame
创建DataFrame对象
DataFrame是一个类似于二维数组或表格(如excel)的对象,既有行索引,又有列索引
- 表示沿着行的方向(垂直向下)跨越,对每一列进行操作,结果是行数被修改了。==0轴,axis=0==,等价于
axis='index' - 表示沿着列的方向(水平向右)跨越,对每一行进行操作,结果是列数被修改了。==1轴,axis=1==,等价于
axis='columns'

DataFrame的创建有很多种方式
读取文件数据返回df:在之前的学习中我们使用了pd.read_csv('csv格式数据文件路径')的方式获取了df对象
使用字典、列表、元组创建df:接下来就展示如何使用字典、列表+元组、numpy创建df对象
- 1- 使用字典加列表创建df,使默认自增索引
df1_data = {
'日期': ['2021-08-21', '2021-08-22', '2021-08-23'],
'温度': [25, 26, 50],
'湿度': [81, 50, 56]
}
df1 = pd.DataFrame(data=df1_data)
df1
# 返回结果如下
日期 温度 湿度
0 2021-08-21 25 81
1 2021-08-22 26 50
2 2021-08-23 50 56
- 2- 使用列表加元组创建df,并自定义索引
df2_data = [
('2021-08-21', 25, 81),
('2021-08-22', 26, 50),
('2021-08-23', 27, 56)
]
df2 = pd.DataFrame(
data=df2_data,
columns=['日期', '温度', '湿度'],
index = ['row_1','row_2','row_3'] # 手动指定索引
)
df2
# 返回结果如下
日期 温度 湿度
row_1 2021-08-21 25 81
row_2 2021-08-22 26 50
row_3 2021-08-23 27 56
- 3- 使用numpy创建df
通过已有数据创建
pd.DataFrame(np.random.randint(40, 100, (10, 5)))
创建学生成绩表
# 生成10名同学,5门功课的数据
score = np.random.randint(40, 100, (10, 5))
# 结果
array([[92, 55, 78, 50, 50],
[71, 76, 50, 48, 96],
[45, 84, 78, 51, 68],
[81, 91, 56, 54, 76],
[86, 66, 77, 67, 95],
[46, 86, 56, 61, 99],
[46, 95, 44, 46, 56],
[80, 50, 45, 65, 57],
[41, 93, 90, 41, 97],
[65, 83, 57, 57, 40]])
但是这样的数据形式很难看到存储的是什么的样的数据,可读性比较差!
问题:如何让数据更有意义的显示?
# 使用Pandas中的数据结构
score_df = pd.DataFrame(score)

给分数数据增加行列索引,显示效果更佳
效果:

增加行、列索引
# 构造列索引序列
subjects = ["语文", "数学", "英语", "政治", "体育"]
# 构造行索引序列
stu = ['同学' + str(i) for i in range(score_df.shape[0])]
# 添加索引
data = pd.DataFrame(score, columns=subjects, index=stu)
DataFrame对象属性
属性
- 1- shape属性
data.shape
# 结果
(10, 5)
- 2- index属性
DataFrame的行索引列表
data.index
# 结果
Index(['同学0', '同学1', '同学2', '同学3', '同学4', '同学5', '同学6', '同学7', '同学8', '同学9'], dtype='object')
- 3- columns
data.columns
# 结果
Index(['语文', '数学', '英语', '政治', '体育'], dtype='object')
- 4- values
直接获取其中array的值
data.values
array([[92, 55, 78, 50, 50],
[71, 76, 50, 48, 96],
[45, 84, 78, 51, 68],
[81, 91, 56, 54, 76],
[86, 66, 77, 67, 95],
[46, 86, 56, 61, 99],
[46, 95, 44, 46, 56],
[80, 50, 45, 65, 57],
[41, 93, 90, 41, 97],
[65, 83, 57, 57, 40]])
- 5- T
转置:行转列
data.T
结果

DataFrame对象方法
1- head(n)
显示前n行内容
data.head(5)

2- tail(n)
显示后n行内容
如果不补充参数,默认5行。填入参数n则显示后n行
data.tail(5)
….其他的方法自己去测试…
DataFrame索引的设置

- 1- 修改行列索引
stu = ["学生_" + str(i) for i in range(score_df.shape[0])]
# 必须整体全部修改
data.index = stu
注意:以下修改方式是错误的
# 错误修改方式
data.index[3] = '学生_3'
可能遇到的错误:

原因: DataFrame的索引修改只能整体进行修改,不能修改部分。但是我们可以通过rename重命名的方式修改部分索引,例如:
data.rename(index={'Avatar':'avatar'},inplace=True)
代码表示的意思是将行索引的旧名称Avatar改成新名称avatar
- 2- 重设索引
- reset_index(drop=False)
- 设置新的下标索引
- drop:默认为False,不删除原来索引,如果为True,删除原来的索引值
- reset_index(drop=False)
# 重置索引,drop=False
data.reset_index()

# 重置索引,drop=True
data.reset_index(drop=True)
- 3- 以某列值设置为新的索引
- set_index(keys, drop=True)
- keys : 列索引名成或者列索引名称的列表
- drop : boolean, default True.当做新的索引,删除原来的列
- set_index(keys, drop=True)
设置新索引案例
第一步:创建
df = pd.DataFrame({'month': [1, 4, 7, 10],
'year': [2012, 2014, 2013, 2014],
'sale':[55, 40, 84, 31]})
month sale year
0 1 55 2012
1 4 40 2014
2 7 84 2013
3 10 31 2014
第二步:以月份设置新的索引
df.set_index('month')
sale year
month
1 55 2012
4 40 2014
7 84 2013
10 31 2014
第三步:设置多个索引,以年和月份
df = df.set_index(['year', 'month'])
df
sale
year month
2012 1 55
2014 4 40
2013 7 84
2014 10 31
Pandas的数据类型
df或s对象中具体每一个值的数据类型有很多,如下表所示
| Pandas数据类型 | 说明 | 对应的Python类型 |
|---|---|---|
| object | ==字符串类型== | string |
| int | 整数类型 | int |
| float | 浮点数类型 | float |
| datetime | 日期时间类型 | datetime包中的datetime类型 |
| timedelta | 时间差类型 | datetime包中的timedelta类型 |
| category | 分类类型 | 无原生类型,可以自定义 |
| bool | 布尔类型 | bool(True,False) |
| nan | 空值类型 | None |
- 可以通过下列API查看s对象或df对象中数据的类型
s1.dtypes
df1.dtypes
df1.info()
c
- datetime类型
import pandas as pd # 创建一个datetime类型的Series dates = pd.to_datetime(['2024-09-01', '2024-09-02', '2024-09-03']) print(dates)- timedelta类型
import pandas as pd # 计算两个日期之间的差值 start_date = pd.to_datetime('2024-09-01') end_date = pd.to_datetime('2024-09-05') delta = end_date - start_date print(delta)- category类型
类型用于表示分类数据,通常用于有限集合中的数据类型,例如性别、颜色、产品类型等。这种类型的优点在于占用更少的内存,并且对分类数据的操作更快。
import pandas as pd # 创建一个category类型的Series categories = pd.Series(['apple', 'banana', 'apple', 'orange'], dtype='category') print(categories)
三、【掌握】Pandas基本数据操作
掌握内容
1- 索引操作
loc[行索引名称开始:行索引名称结束, 列索引名称开始:列索引名称结束],注意左右都是闭区间
iloc[行索引下标开始:行索引下标结束, 列索引下标开始:列索引下标结束],注意左闭右开
2- 赋值操作
DataFrame对象['字段名称'] = 字段值
3- 排序操作
sort_index()
数据集
为了更好的理解这些基本操作,我们将读取一个真实的股票数据。关于文件操作,后面在介绍,这里只先用一下API
# 读取文件
data = pd.read_csv("./data/stock_day.csv")
# 删除一些列,让数据更简单些,再去做后面的操作
data = data.drop(["ma5","ma10","ma20","v_ma5","v_ma10","v_ma20"], axis=1)

索引操作【重点】
直接使用行列索引(==先列后行==)
获取’2018-02-27’这天的’open’的结果
# 第1种:通过行列索引值取内容
# 注意:必须是先写列,再写行
df2["volume"]["2018-02-22"]
# 其他错误的写法
# 行索引放错位置了
# df2["2018-02-22"]["volume"]
# 该方式不支持索引下标
df2[1][1]
结合loc或者iloc使用索引(==先行后列==)
获取从’2018-02-27’:’2018-02-22’,’open’的结果
# 第2种:通过loc和行列索引值取内容
# 语法总结:[行索引开始名称:行索引结束名称, 列索引开始名称:列索引结束名称],注意左右都是闭区间
df2.loc["2018-02-22","volume"]
df2.loc["2018-02-26":"2018-02-22","volume"]
df2.loc["2018-02-26":"2018-02-23","close":"low"]
# 第3种:通过iloc和行列索引下标取内容
# 语法总结:[行索引开始下标:行索引结束下标, 列索引开始下标:列索引结束下标],注意左闭右开
df2.iloc[2,1] # 23.37
df2.iloc[2,1:3] # 23.37 22.82
df2.iloc[1:3, 1:4]
赋值操作【重点】
对DataFrame当中的close列进行重新赋值为1
# 推荐。直接修改原来的值
data['close'] = 1
# 或者
data.close = 1
排序操作
排序有两种形式,一种对于索引进行排序,一种对于内容进行排序
DataFrame排序
- 使用df.sort_values(by=, ascending=)
- 单个键或者多个键进行排序,
- 参数:
- by:指定排序参考的键
- ascending:默认升序
- ascending=False:降序
- ascending=True:升序
# 按照开盘价大小进行排序 , 使用ascending指定按照大小排序
data.sort_values(by="open", ascending=True).head()

# 按照多个键进行排序
data.sort_values(by=['open', 'high'])

- 使用df.sort_index给索引进行排序
这个股票的日期索引原来是从大到小,现在重新排序,从小到大
# 对索引进行排序
data.sort_index()

Series排序
- 使用series.sort_values(ascending=True)进行排序
series排序时,只有一列,不需要参数
data['p_change'].sort_values(ascending=True).head()
2015-09-01 -10.03
2015-09-14 -10.02
2016-01-11 -10.02
2015-07-15 -10.02
2015-08-26 -10.01
Name: p_change, dtype: float64
- 使用series.sort_index()进行排序
与df一致
# 对索引进行排序
data['p_change'].sort_index().head()
2015-03-02 2.62
2015-03-03 1.44
2015-03-04 1.57
2015-03-05 2.02
2015-03-06 8.51
Name: p_change, dtype: float64
四、【理解】DataFrame运算
掌握内容
describe():对数据的整体情况进行了解
apply():数据自定义处理函数
算术运算
- add(other)
比如进行数学运算加上具体的一个数字
# ---------------- 算术运算 ----------------
df1 = df.add(100) # DF中所有单元格的数字全部都加上指定的参数值
df1 = df["open"].add(1000) # 从DF中取出一列进行计算
df1
- sub(other)
比如进行数学运算减去具体的一个数字
df2 = df["open"].sub(1000) # 减法
df2 = df["open"].div(100) # 除法
df2 = df["open"].mul(100) # 乘法
# 同时支持算术运算的符号
df2 = df["open"] * 10000 # 乘法
df2 = df["open"] + 10000 # 加法
df2
逻辑运算符
总结:
1- 逻辑运算中不能使用单词and、or、not,必须使用对应的符号& | ~
2- 每个比较运算符表达式必须使用小括号包起来
逻辑运算符号
- 例如筛选data[“open”] > 23的日期数据
- data[“open”] > 23返回逻辑结果
# ---------------- 逻辑运算 ----------------
# 数据大小判断
df["open"]>22
# 将判断语句作为过滤条件,对数据进行过滤。True的保留;False的不要
# 类似SQL中的写法:select * from df where open>22
df[df["open"]>22]

- 完成多个逻辑判断
# 能够写多个逻辑表达式
# 类似SQL中的写法:select * from df where open>22 and close<=22
# 下面是错误的写法,会报错:The truth value of a Series is ambiguous
# df[df["open"]>22 and df["close"]<=22]
"""
注意事项:如下两个是必须要满足
1- 不能使用逻辑运算的单词and、or、not,需要改成对应的符号&、|、~
2- 每个比较运算表达式要用小括号包起来
"""
# and
# 正确写法
df[(df["open"]>22) & (df["close"]<=22)]
# or
# 错误写法
# df[(df["open"]>22) or (df["close"]<=22)]
# 正确的写法
df[(df["open"]>22) | (df["close"]<=22)]
# not
# 错误写法
# df[not df["open"]>22]
# 正确的写法
df[~(df["open"]>22)]

逻辑运算函数
- query(expr)
- expr:查询字符串
通过query使得刚才的过程更加方便简单
# 简单的形式,使用query函数
df.query("open>22 and close<=22 and low>=21")
df.query("open>22 or close<=22")
df.query("not open>22")
df.query("open in (23.53,22.80)")
# 注意:判断是否相等需要使用两个等号
df.query("open==23.53")
- isin(values)
例如判断’open’是否为23.53和23.85
# 可以指定值进行一个判断,从而进行筛选操作
data[data["open"].isin([23.53, 23.85])]
统计运算【理解】
describe
综合分析: 能够直接得出很多统计结果,count, mean, std, min, max 等
# 计算平均值、标准差、最大值、最小值
data.describe()


统计函数
Numpy当中已经详细介绍,在这里我们演示min(最小值), max(最大值), mean(平均值), median(中位数), var(方差), std(标准差),mode(众数)结果:
count | Number of non-NA observations |
|---|---|
sum | Sum of values |
mean | Mean of values |
median | Arithmetic median of values |
min | Minimum |
max | Maximum |
mode | Mode(众数) |
abs | Absolute Value |
prod | Product of values(乘积) |
std | Bessel-corrected sample standard deviation |
var | Unbiased variance |
idxmax | compute the index labels with the maximum |
idxmin | compute the index labels with the minimum |
对于单个函数去进行统计的时候,坐标轴还是按照默认列“columns” (axis=0, default),如果要对行“index” 需要指定(axis=1)
- max()、min()
# 使用统计函数
data.max(0)
open 34.99
high 36.35
close 35.21
low 34.01
volume 501915.41
price_change 3.03
p_change 10.03
turnover 12.56
my_price_change 3.41
dtype: float64
- std()、var()
# 方差
data.var(0)
open 1.545255e+01
high 1.662665e+01
close 1.554572e+01
low 1.437902e+01
volume 5.458124e+09
price_change 8.072595e-01
p_change 1.664394e+01
turnover 4.323800e+00
my_price_change 6.409037e-01
dtype: float64
# 标准差
data.std(0)
open 3.930973
high 4.077578
close 3.942806
low 3.791968
volume 73879.119354
price_change 0.898476
p_change 4.079698
turnover 2.079375
my_price_change 0.800565
dtype: float64
- median():中位数
中位数为将数据从小到大排列,在最中间的那个数为中位数。如果没有中间数,取中间两个数的平均值。
df = pd.DataFrame({'COL1' : [2,3,4,5,4,2], 'COL2' : [0,1,2,3,4,2]})
df.median()
COL1 3.5
COL2 2.0
dtype: float64
- idxmax()、idxmin()
# 求出最大值的位置
data.idxmax(axis=0)
open 2015-06-15
high 2015-06-10
close 2015-06-12
low 2015-06-12
volume 2017-10-26
price_change 2015-06-09
p_change 2015-08-28
turnover 2017-10-26
my_price_change 2015-07-10
dtype: object
# 求出最小值的位置
data.idxmin(axis=0)
open 2015-03-02
high 2015-03-02
close 2015-09-02
low 2015-03-02
volume 2016-07-06
price_change 2015-06-15
p_change 2015-09-01
turnover 2016-07-06
my_price_change 2015-06-15
dtype: object
累计统计函数【了解】
| 函数 | 作用 |
|---|---|
cumsum | 计算前1/2/3/…/n个数的和 |
cummax | 计算前1/2/3/…/n个数的最大值 |
cummin | 计算前1/2/3/…/n个数的最小值 |
cumprod | 计算前1/2/3/…/n个数的积 |
那么这些累计统计函数怎么用?

以上这些函数可以对series和dataframe操作
这里我们按照时间的从前往后来进行累计
- 排序
# 排序之后,进行累计求和
data = data.sort_index()
- 对p_change进行求和
stock_rise = data['p_change']
# plot方法集成了前面直方图、条形图、饼图、折线图
stock_rise.cumsum()
那么如何让这个连续求和的结果更好的显示呢?

如果要使用plot函数,需要导入matplotlib.
import matplotlib.pyplot as plt
# plot显示图形
stock_rise.cumsum().plot()
# 需要调用show,才能显示出结果
plt.show()
apply自定义运算
- apply(func, axis=0)
- func:自定义函数
- axis=0:默认是列,axis=1为行进行运算
- 定义一个对列,最大值-最小值的函数
data[['open', 'close']].apply(lambda x: x.max() - x.min(), axis=0)
五、【掌握】文件读取与存储
掌握内容
read_csv()
read_sql()
read_json():推荐参数组合orient="records",lines=True
to_csv()
to_sql()
to_json():推荐参数组合orient="records",lines=True
我们的数据大部分存在于文件当中,所以pandas会支持复杂的IO操作,pandas的API支持众多的文件格式,如CSV、SQL、XLS、JSON、HDF5。
注:最常用的sql和CSV文件

CSV
read_csv
- pandas.read_csv(filepath_or_buffer, sep =’,’, usecols )
- filepath_or_buffer:文件路径
- sep :分隔符,默认用”,”隔开
- usecols:指定读取的列名,列表形式
- 举例:读取之前的股票的数据
# 读取文件,并且指定只获取'open', 'close'指标
data = pd.read_csv("./data/stock_day.csv", usecols=['open', 'close'])
to_csv
- DataFrame.to_csv(path_or_buf=None, sep=’, ’, columns=None, header=True, index=True, mode=’w’, encoding=None)
- path_or_buf :文件路径
- sep :分隔符,默认用”,”隔开
- columns :选择需要的列索引
- header : 是否要将字段名称输出到文件中作为第一行。推荐使用True
- index:是否写进行索引,是否写进列索引值
- mode:’w’:覆盖写, ‘a’ 追加
- encoding:输出的文件编码方式。推荐使用UTF-8
- 举例:保存读取出来的股票数据
- 保存’open’列的数据,然后读取查看结果
"""
参数解释:
filepath_or_buffer:文件路径
sep:字段值之间的分隔符
encoding:文件的编码方式
usecols:将哪些字段读取出来
"""
# 读取
df1 = pd.read_csv(filepath_or_buffer="data/stock_day.csv",sep=",",encoding="UTF-8")
df1
# 指定将哪些字段读取出来
df2 = pd.read_csv(filepath_or_buffer="data/stock_day.csv",sep=",",encoding="UTF-8",usecols=["open","high"])
df2
会发现将索引存入到文件当中,变成单独的一列数据。如果需要删除,可以指定index参数,删除原来的文件,重新保存一次。
"""
参数解释
path_or_buf:文件的写出路径
sep:字段值之间的分隔符
mode:写出模式。w覆盖写,a追加写
encoding:文件的编码方式
columns:要将哪些字段写出到文件中
header:是否在文件的第一行记录字段名称。推荐设置为True
index:是否将索引列的内容写出到文件
"""
# 将数据写出成csv文件
df1.to_csv(path_or_buf="data/stock_to_csv.csv",sep="\t",mode="w",encoding="UTF-8")
df1.to_csv(
path_or_buf="data/stock_to_csv_2.csv",
sep="\t",
mode="w",
encoding="UTF-8",
columns=["open","close","high"],
header=False,
index=False
)
MySQL
以MySQL数据库为例,此时默认你已经安装好了MySQL数据库。如果想利用pandas和MySQL数据库进行交互,需要先安装与数据库交互所需要的python包
pip install pymysql==1.0.2 -i https://pypi.tuna.tsinghua.edu.cn/simple/
# 如果后边的代码运行提示找不到sqlalchemy的包,和pymysql一样进行安装即可
pip install sqlalchemy==2.0.30 -i https://pypi.tuna.tsinghua.edu.cn/simple/

- 准备要写入数据库的数据
import pandas as pd
"""
index_col:指定使用文件中哪个索引的字段作为DataFrame的索引
"""
# 准备数据
df2 = pd.read_csv("data/csv示例文件.csv",sep=",",encoding="GBK",index_col=[0])
df2

- 创建数据库操作引擎对象并指定数据库
# 创建数据库连接
from sqlalchemy import create_engine
# 格式 数据库产品类型+开源库://用户名:密码@ip地址:端口号/数据库名称?其他参数设置
engine = create_engine("mysql+pymysql://root:root@127.0.0.1:3306/db4?charset=utf8")
- 将数据写入MySQL数据库
# 写入到数据库
"""
参数解释:
name:要写入的表名。如果不存在,自动创建
con:connection,数据库连接
if_exists:如果表或者数据已经存在的处理方式
append:追加写
replace:覆盖写
fail:如果表存在,直接报错
"""
# df2.to_sql(name="sql_test",con=engine,if_exists="replace",index=False)
# df2.to_sql(name="sql_test",con=engine,if_exists="append",index=False)
df2.to_sql(name="sql_test",con=engine,if_exists="fail",index=False)
此时我们就可以在本地test库的test_pdtosql表中看到写入的数据

从数据库中加载数据:
- 读取整张表, 返回dataFrame
# 读取数据表 df3 = pd.read_sql(sql="sql_test",con=engine)- 使用SQL语句获取数据,返回dataframe
# 传入sql语句,传入数据库连接引擎对象 df4 = pd.read_sql(sql="select * from sql_test where name='赵金龙' ",con=engine)
可能出现的问题:

说明:
sqlalchemy 库版本过低导致的
解决方案:
先删除原有版本:
pip uninstall sqlalchemy
重新安装:
pip install sqlalchemy==2.0.30
sqlalchemy官网:https://www.sqlalchemy.org/
数据库连接如何写:https://docs.sqlalchemy.org/en/20/dialects/mysql.html#module-sqlalchemy.dialects.mysql.pymysql
JSON
JSON在线解析网站:https://www.sojson.com/
JSON只能使用双引号。而Python中的字典单引号、双引号都行
JSON是我们常用的一种数据交换格式,前面在前后端的交互经常用到,也会在存储的时候选择这种格式。所以我们需要知道Pandas如何进行读取和存储JSON格式。
read_json
pandas.read_json(path_or_buf=None, orient=None, typ=’frame’, lines=False)
将JSON格式转换成默认的Pandas DataFrame格式
orient : string,Indication of expected JSON string format.
split : JSON内容包含索引、值、列名
records: ==推荐使用==。每条数据形成一个字典
index: 每条数据形成一个字典。并且索引作为key,列和值形成的字典作为value
columns: key是列名,value是索引和值形成的字典
values: 只输出数据值,不带索引和列名
lines : ==推荐使用True==如果为 True,则每一行是一个独立的 JSON 对象
typ : 指定转换成的对象类型series或者dataframe
read_json案例
- 数据介绍
这里使用一个新闻标题数据集,格式为json。存储格式为:
{"article_link": "https://www.huffingtonpost.com/entry/versace-black-code_us_5861fbefe4b0de3a08f600d5", "headline": "former versace store clerk sues over secret 'black code' for minority shoppers", "is_sarcastic": 0}
{"article_link": "https://www.huffingtonpost.com/entry/roseanne-revival-review_us_5ab3a497e4b054d118e04365", "headline": "the 'roseanne' revival catches up to our thorny political mood, for better and worse", "is_sarcastic": 0}
- 读取
orient指定存储的json格式,lines指定按照行去变成一个样本
"""
参数解释:
path_or_buf:文件路径
orient:JSON格式。推荐使用records,那么JSON的格式就是键值对的形式
lines:数据中每一行试一个单独的JSON
"""
json_read = pd.read_json("./data/Sarcasm_Headlines_Dataset.json", orient="records", lines=True)
结果:

to_json
- DataFrame.to_json(path_or_buf=None,orient=None,lines=False)
- 将Pandas 对象存储为json格式
- path_or_buf=None:文件地址
- orient:存储的json形式,{‘split’,’records’,’index’,’columns’,’values’}==推荐使用records==
- lines:一个对象存储为一行==推荐使用True==
案例
- 存储文件
json_read.to_json("./data/test.json", orient='records')
结果
[{"article_link":"https:\/\/www.huffingtonpost.com\/entry\/versace-black-code_us_5861fbefe4b0de3a08f600d5","headline":"former versace store clerk sues over secret 'black code' for minority shoppers","is_sarcastic":0},{"article_link":"https:\/\/www.huffingtonpost.com\/entry\/roseanne-revival-review_us_5ab3a497e4b054d118e04365","headline":"the 'roseanne' revival catches up to our thorny political mood, for better and worse","is_sarcastic":0},{"article_link":"https:\/\/local.theonion.com\/mom-starting-to-fear-son-s-web-series-closest-thing-she-1819576697","headline":"mom starting to fear son's web series closest thing she will have to grandchild","is_sarcastic":1},{"article_link":"https:\/\/politics.theonion.com\/boehner-just-wants-wife-to-listen-not-come-up-with-alt-1819574302","headline":"boehner just wants wife to listen, not come up with alternative debt-reduction ideas","is_sarcastic":1},{"article_link":"https:\/\/www.huffingtonpost.com\/entry\/jk-rowling-wishes-snape-happy-birthday_us_569117c4e4b0cad15e64fdcb","headline":"j.k. rowling wishes snape happy birthday in the most magical way","is_sarcastic":0},{"article_link":"https:\/\/www.huffingtonpost.com\/entry\/advancing-the-worlds-women_b_6810038.html","headline":"advancing the world's women","is_sarcastic":0},....]
- 修改lines参数为True
json_read.to_json("./data/test.json", orient='records', lines=True)
结果
{"article_link":"https:\/\/www.huffingtonpost.com\/entry\/versace-black-code_us_5861fbefe4b0de3a08f600d5","headline":"former versace store clerk sues over secret 'black code' for minority shoppers","is_sarcastic":0}
{"article_link":"https:\/\/www.huffingtonpost.com\/entry\/roseanne-revival-review_us_5ab3a497e4b054d118e04365","headline":"the 'roseanne' revival catches up to our thorny political mood, for better and worse","is_sarcastic":0}
{"article_link":"https:\/\/local.theonion.com\/mom-starting-to-fear-son-s-web-series-closest-thing-she-1819576697","headline":"mom starting to fear son's web series closest thing she will have to grandchild","is_sarcastic":1}
{"article_link":"https:\/\/politics.theonion.com\/boehner-just-wants-wife-to-listen-not-come-up-with-alt-1819574302","headline":"boehner just wants wife to listen, not come up with alternative debt-reduction ideas","is_sarcastic":1}
{"article_link":"https:\/\/www.huffingtonpost.com\/entry\/jk-rowling-wishes-snape-happy-birthday_us_569117c4e4b0cad15e64fdcb","headline":"j.k. rowling wishes snape happy birthday in the most magical way","is_sarcastic":0}...
- 其他参数值演示
json_read.to_json(path_or_buf="data/copy2_json.json",orient="records",lines=False)
json_read.to_json(path_or_buf="data/copy3_json.json",orient="split",lines=False)
json_read.to_json(path_or_buf="data/copy4_json.json",orient="index",lines=False)
六、【熟悉】DataFrame数据的增删改查操作
掌握内容
1- 增加新字段
DataFrame对象["新字段名称"] = 字段值
2- 删除字段
drop()
3- 去重
unique()
4- 修改
DataFrame对象["字段名称"] = 新的字段值
- 导包并加载数据:
import pandas as pd
df = pd.read_csv('data/1960-2019全球GDP数据.csv', encoding='gbk')
df2 = df.head()
增加列
- 方式一: 通过直接赋值的方式添加新列【掌握】
# 拷贝一份df
df3 = df2.copy()
# 一列数据都是固定值
df3['new col 1'] = 33
# 新增列数据数量必须和行数相等
df3['new col 2'] = [1, 2, 3, 4, 5]
df3['new col 3'] = df3.year * 2
# 分别查看增加数据列之后的df和原df
df3
df2
- 方式二: df.assign函数添加列
# 1. 新列名=单个数据或一组数据,一组数据的数量必须和df的行数相同
df2.assign(new0=66)
# df2.assign(new1=[1, 2, 3, 4]) # 报错
df2.assign(new1=[1, 2, 3, 4, 5])
# 2.1 新列名=Series对象,该s对象的索引和df索引一致
s = pd.Series([1, 2, 3, 4, 5])
df2.assign(new2=s)
# 2.2 新列名=Series对象
df2.assign(new3=df2.year+df2.GDP)
# 3. 新列名=自定义函数名
# 该自定义函数必须接收df作为参数
# 该自定义函数可以返回:
# 3.1.单个数据
# 3.2.一组数量和df的行数相同的数据
# 3.3.和df索引相同的Series对象
def foo(df):
# 函数必须接收一个参数,该参数就是被传入的df对象
print('='*10)
print(df)
print('='*5 + '上面输出的是传入的df')
ret = df.index.values
# 可以返回一个变量
# return 'hahah'
# 也可以返回一组变量
return ret
df2.assign(new4=foo)
解析:当我们使用函数的方式向df对象添加新列时,foo函数要求必须有一个参数,这个参数来自于调用assign的df对象,如上图所示
当我们给foo(df)定义一个df参数时,当assign方法开始执行,则系统会自动将df2对象作为参数传递给foo函数中的df参数
- df.assign函数可以同时添加多列
# assign特殊
def fn1(my_df):
return 22
def fn2(my_df):
return my_df.GDP/1000000+my_df.year
df_5 = df_4.assign(
new1=100,
new2=[11,22,33,44,55],
new3=df_4.year*100,
new4=fn1,
new5=fn2
)
df_5
删除与去重
- 1- df.drop删除行数据
df3.drop([0]) # 默认删除行
df3.drop([0, 2, 4]) # 可以删除多行
df3.GDP.drop([0, 2]) # 对series对象按索引删除
- 2- df.drop删除列数据【掌握】
- df.drop默认删除指定索引值的行;如果添加参数
axis=1,则删除指定列名的列
- df.drop默认删除指定索引值的行;如果添加参数
df3.drop(['new col 3'], axis=1)
- 3- 使用del删除指定的列
- 注意区别:
- del是直接永久删除原df中的列【慎重使用】
- drop是返回删除后的df或seires,原df或seires没有被修改
- 注意区别:
del df3['new col 3']
df3
# 重复运行本段代码将会报错,因为df3中的指定列在第一次运行时就被删除了
- 4- Dataframe数据去重
# 添加一部分重复的数据
df6 = pd.concat([df5, df5], axis=0).reset_index(drop=True)
# keep:保留重复数据中的第几条。first表示第一条;last表示最后一条
# df7 = df6.drop_duplicates(keep="first")
df7 = df6.drop_duplicates(keep="last")
df7

- 5- series去重
# Series数据去重
from pandas import Series
s2:Series = df6.country
s3 = s2.drop_duplicates() # 返回结果数据类型 是 Series对象
s3
arr1 = s2.unique() # 返回结果数据类型 是 Numpy中的ndarray
arr1
修改DataFrame中的数据
- 1- df.assign替换列
df = pd.read_csv('data/1960-2019全球GDP数据.csv', encoding='GBK')
df5 = df.head()
df5
df5.assign(GDP=66) # 可以接收单变量或列表、数组
df5 # 此时原始的df5不会发生改变
- 2- 直接对原始的DF进行赋值修改处理
df = pd.read_csv('../数据集/1960-2019全球GDP数据.csv', encoding='gbk', )
df5 = df.head()
df5
df5['GDP'] = [5, 4, 3, 2, 1]
df5 # 此时原始的df5会发生改变
- 3- replace函数替换数据
# replace替换函数:DF、Series对象都有该方法。只能够整体匹配然后替换,不能局部替换
df10 = df.head().copy()
df11 = df10.replace(to_replace="日本",value="扶桑")
df11
# 只能够整体匹配然后替换,不能局部替换
s4 = df10.year.replace(to_replace=1960,value=196666)
# s4 = df10.year.replace(to_replace=196,value=1999999)
# s4 = df10.country.replace(to_replace="日",value="sun")
s4
查询dataFrame中的数据
1- 从前从后取多行数据
- head()
# 导包 import pandas as pd # 加载csv数据,指定gbk编码格式来读取文件,返回df df = pd.read_csv('../数据集/1960-2019全球GDP数据.csv', encoding='gbk') # 默认取前5行数据 df.head() df.head(10) # 取前10行- tail()
# 默认取后5行数据 df.tail() df2 = df.tail(15) # 倒数15行 df22- 获取一列或多列数据
- 获取一列数据
df[col_name]等同于df.col_name
# 下面两种方式获得的结果类型都是Series对象 df2['country'] df2.country # 注意!如果列名字符串中间有空格的,只能使用df['country']这种形式- 获取多列数据
df[[col_name1,col_name2,...]]
df2[['country', 'GDP']] # 返回新的df- 获取一列数据
3- 索引下标切片取行
df[start:stop:step]:
df[start:stop:step]==df[起始行下标:结束行下标:步长], 遵循顾头不顾尾原则(包含起始行,不包含结束行),步长默认为1df4 = df.head(10) # 取原df前10行数据作为df4,默认自增索引由0到9 df4[0:3] # 取前3行 df4[:5:2] # 取前5行,步长为2 df4[1::3] # 取第2行到最后所有行,步长为34- 查询函数获取子集: df.query()
df.query(判断表达式)可以依据判断表达式返回的符合条件的df子集- 与
df[布尔值向量]效果相同 - 特别注意
df.query()中传入的字符串格式
- 示例:
df3.query('country=="帕劳"') df3[df3['country']=='帕劳']- 查询中国, 美国 日本 三国 2015年至2019年的数据
df.query('country=="中国" or country=="日本" or country=="美国"').query('year in ["2015", "2016", "2017", "2018", "2019"]') df.query('(country=="中国" or country=="日本" or country=="美国") and year in ["2015", "2016", "2017", "2018", "2019"]')5- 排序函数
- sort_values函数: 按照指定的一列或多列的值进行排序
# 按GDP列的数值由小到大进行排序 df2.sort_values(['GDP']) # 按GDP列的数值由大到小进行排序 df2.sort_values(['GDP'], ascending=False) # 倒序, ascending默认为True # 先对year年份进行由小到大排序,再对GDP由小到大排序 df2.sort_values(['year', 'GDP'])- rank函数【了解】:
- rank函数用法:
DataFrame.rank()或Series.rank() - rank函数返回值:以Series或者DataFrame的类型返回数据的排名(哪个类型调用返回哪个类型)
- rank函数包含有6个参数:
- axis:设置沿着哪个轴计算排名(0或者1),默认为0按纵轴计算排名
- numeric_only:是否仅仅计算数字型的columns,默认为False
- na_option :NaN值是否参与排序及如何排序,固定参数:keep top bottom
- keep: NaN值保留原有位置
- top: NaN值全部放在前边
- bottom: NaN值全部放在最后
- ascending:设定升序排还是降序排,默认True升序
- pct:percent是否以排名的百分比显示排名(所有排名与最大排名的百分比),默认False
- method:排名评分的计算方式,固定值参数,常用固定值如下:
- average : 默认值,排名评分不连续;数值相同的评分一致,都为平均值
- min : 排名评分不连续;数值相同的评分一致,都为最小值
- max : 排名评分不连续;数值相同的评分一致,都为最大值
- dense : 排名评分是连续的;数值相同的评分一致
df2 df2.rank() df2.rank(axis=0) df2.rank(numeric_only=True) # 只对数值类型的列进行统计 df2.rank(ascending=False) # 降序 df2.rank(pct=True) # 以最高分作为1,放回百分数形式的评分,pct参数默认为False
df2.rank(method='average') df2.rank(method='min') df2.rank(method='max') df2.rank(method='dense')
rank使用详解
df7 = pd.DataFrame({ '姓名':['小明', '小美', '小强', '小兰'], '成绩':[100, 90, 90, 80] }) df7.rank() # 等价于df7.rank(method='average', ascending=True) df7['成绩排名'] = df7.成绩.rank(method='min', ascending=False) df7 df7['成绩排名'] = df7.成绩.rank(method='max', ascending=False) df7 df7['成绩排名'] = df7.成绩.rank(method='dense', ascending=False) df7 最后一种希望大家重点掌握,其他几种搞清楚原理即可。6- 聚合函数:
常用聚合函数有:
- min 最小值
- max 最大值
- mean 平均值
- sum 求和
- count 求个数
- min函数
df2.min() df2['year'].min()- max函数
df2.max() df2['year'].max()- mean 平均值
df2.mean() df2['year'].mean() df2['GDP'].mean()
七、【掌握】高级处理-缺失值处理
掌握内容
fillna():填充缺失值
dropna(axis=0):删除带有缺失值的行
如何处理NAN
获取缺失值的标记方式(nan或者其他标记方式)
如果缺失值的标记方式是nan
- 判断数据中是否包含nan:
- pd.isnull(df),
- pd.notnull(df)
- 存在缺失值nan:
- 1、删除存在缺失值的:dropna(axis)
- 注:不会修改原数据,需要接受返回值
- 2、替换缺失值:fillna(value, inplace=True)
- value:替换成的值
- inplace:True:会修改原数据,False:不替换修改原数据,生成新的对象
- 1、删除存在缺失值的:dropna(axis)
- 判断数据中是否包含nan:
如果缺失值没有使用nan标记,比如使用”?”
- 先替换‘?’为np.nan,然后继续处理
电影数据的缺失值处理
- 电影数据文件获取
# 读取电影数据
movie = pd.read_csv("data/movie.csv")

判断缺失值是否存在
- pd.notnull(DataFrame对象):等同于SQL语句中的 is not null
pd.notnull(movie)
Rank Title Genre Description Director Actors Year Runtime (Minutes) Rating Votes Revenue (Millions) Metascore
0 True True True True True True True True True True True True
1 True True True True True True True True True True True True
2 True True True True True True True True True True True True
3 True True True True True True True True True True True True
4 True True True True True True True True True True True True
5 True True True True True True True True True True True True
6 True True True True True True True True True True True True
7 True True True True True True True True True True False True
- np.all(pd.notnull(DataFrame对象))
- pd.isnull(DataFrame对象):等同于SQL语句中的 is null
存在缺失值nan,并且是np.nan
- 1- 删除
pandas删除缺失值,使用dropna的前提是,缺失值的类型必须是np.nan
# 不修改原数据
movie.dropna()
# 可以定义新的变量接受或者用原来的变量名
data = movie.dropna()
- 2- 替换缺失值
# 替换存在缺失值的样本的两列
# 替换填充平均值,中位数
movie['Revenue (Millions)'].fillna(movie['Revenue (Millions)'].mean(), inplace=True)
不是缺失值nan,有默认标记的
数据是这样的:

处理思路分析:
1、先替换‘?’为np.nan
df.replace(to_replace=, value=)
- to_replace:替换前的值
- value:替换后的值
# 替换特殊数据,然后对空值进行处理 df = pd.read_csv("data/breast-cancer-wisconsin.data",encoding="UTF-8") # 链式编程 df4 = df.replace("?",np.nan).dropna() df4
八、【熟悉】高级处理-数据合并
掌握内容
concat:等价于SQL中的union操作
pd.concat实现数据合并
- pd.concat([data1, data2], axis=1):等价于SQL中的union操作
- 按照行或列进行合并
# 拼接concat
"""
参数解释:
objs:参与拼接的数据。可以是DF对象,也可以是Series对象,可以两者混合
axis:0的时候在下面(也就是垂直方向)进行拼接;1的时候在后面(也就是水平方向)进行拼接
"""
con_1 = pd.concat(objs=[df1,df2], axis=0)
con_1 = pd.concat(objs=[df1,df2["A"]], axis=0)
con_1 = pd.concat(objs=[df1,df2], axis=1)
con_1
pd.merge
- pd.merge(left, right, how=’inner’, on=None):等价于SQL中的join操作
- 可以指定按照两组数据的共同键值对合并或者左右各自
left: DataFrameright: 另一个DataFrameon: 指定的共同键- how:按照什么方式连接
| Merge method | SQL Join Name | Description |
|---|---|---|
left | LEFT OUTER JOIN | Use keys from left frame only |
right | RIGHT OUTER JOIN | Use keys from right frame only |
outer | FULL OUTER JOIN | Use union of keys from both frames |
inner | INNER JOIN | Use intersection of keys from both frames |
内连接:
left = pd.DataFrame({'key1': ['K0', 'K0', 'K1', 'K2'],
'key2': ['K0', 'K1', 'K0', 'K1'],
'A': ['A0', 'A1', 'A2', 'A3'],
'B': ['B0', 'B1', 'B2', 'B3']})
right = pd.DataFrame({'key1': ['K0', 'K1', 'K1', 'K2'],
'key2': ['K0', 'K0', 'K0', 'K0'],
'C': ['C0', 'C1', 'C2', 'C3'],
'D': ['D0', 'D1', 'D2', 'D3']})
# 默认内连接
result = pd.merge(left, right, on=['key1', 'key2']) # on t1.key1=t2.key1 and t1.key2=t2.key2

左连接
result = pd.merge(left, right, how='left', on=['key1', 'key2'])

右连接
result = pd.merge(left, right, how='right', on=['key1', 'key2'])

外连接
result = pd.merge(left, right, how='outer', on=['key1', 'key2'])

九、【理解】高级处理-数据分组
数据准备
- 加载优衣库的销售数据集,包含了不同城市优衣库门店的所有产品类别的销售记录,数据字段说明如下
- store_id 门店随机id
- city 城市
- channel 销售渠道 网购自提 门店购买
- gender_group 客户性别 男女
- age_group 客户年龄段
- wkd_ind 购买发生的时间(周末,周间)
- product 产品类别
- customer 客户数量
- revenue 销售金额
- order 订单数量
- quant 购买产品的数量
- unit_cost 成本(制作+运营)
# 导包 加载数据集
import pandas as pd
df = pd.read_csv('data/uniqlo.csv')
groupby分组聚合
1- df.groupby分组函数返回分组对象
【基于一列进行分组】
# 基于顾客性别分组
gs = df.groupby(['gender_group'])
gs
gs['city']
# 返回结果如下
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x000001B1DA988B80>
<pandas.core.groupby.generic.SeriesGroupBy object at 0x000001B1DB2B4FA0>
【基于多列进行分组】
# 基于顾客性别、不同城市分组
gs2 = df.groupby(['gender_group', 'city'])
gs2
# 返回结果如下
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x000001B1DB24F1F0>
2- 分组后获取各个组内的数据
- 2.1 取出每组第一条或最后一条数据
gs2 = df.groupby(['gender_group', 'channel']) gs2.first() # 取出每组第一条数据 gs2.last() # 取出每组最后一条数据
- 2.2 - 按分组依据获取其中一组
gs2.get_group(('Female', '线上'))
3- 分组聚合
- 格式:分组后对多列分别使用不同的聚合函数。agg->aggregate
df.groupby(['列名1', '列名2']).agg({ '指定列1':'聚合函数名', '指定列2':'聚合函数名', '指定列3':'聚合函数名' })- 按城市和线上线下划分,分别计算销售金额的平均值、成本的总和
agg_1 = df.groupby(["city","channel"]).agg( { "revenue":"mean", "unit_cost":"sum" } ) agg_1
分组聚合过滤操作:==Pandas不支持 groupby().agg().filter==
- 格式:
df.groupby(['列名1',...]).filter(
lambda x: dosomething returun True or False
)
案例: 按城市分组,查询每组销售金额平均值大于200的全部数据
df.groupby(['city']).filter(lambda d: d['revenue'].mean() > 200)
df.groupby(['city'])['revenue'].filter(lambda s: s.mean() > 200)
十、【了解】高级处理-交叉表与透视表
交叉表与透视表有什么作用?
探究股票的涨跌与星期几有关?
以下图当中表示,week代表星期几,1,0代表这一天股票的涨跌幅是好还是坏,里面的数据代表比例
可以理解为所有时间为星期一等等的数据当中涨跌幅好坏的比例


交叉表:
交叉表用于计算一列数据对于另外一列数据的分组个数(用于统计分组频率的特殊透视表)
- pd.crosstab(value1, value2)
透视表:
透视表是将原有的DataFrame的列分别作为行索引和列索引,然后对指定的列应用聚集函数
- data.pivot_table()
- DataFrame.pivot_table([], index=[])
交叉表
import pandas as pd
# 创建一个示例数据集
data = {
'性别': ['男', '女', '男', '女', '男', '女', '女', '男'],
'购买': ['是', '否', '是', '是', '否', '否', '是', '否']
}
df = pd.DataFrame(data)
# 创建交叉表
crosstab = pd.crosstab(df['性别'], df['购买'])
print(crosstab)
购买 否 是
性别
女 2 2
男 2 2
透视表
import pandas as pd
data = {
'性别': ['男', '女', '男', '女', '男', '女'],
'购买': ['是', '否', '是', '是', '否', '否'],
'金额': [100, 150, 200, 130, 160, 120]
}
df = pd.DataFrame(data)
# 创建透视表
pivot_table = pd.pivot_table(df, values='金额', index='性别', columns='购买', aggfunc='mean')
print(pivot_table)
购买 否 是
性别
女 135.0 130.0
男 180.0 150.0
案例分析
数据准备
- 准备两列数据,星期数据以及涨跌幅是好是坏数据
- 进行交叉表计算
# 寻找星期几跟股票张得的关系
# 1、先把对应的日期找到星期几
date = pd.to_datetime(data.index).weekday
data['week'] = date
# 2、假如把p_change按照大小去分个类0为界限
data['posi_neg'] = np.where(data['p_change'] > 0, 1, 0)
# 通过交叉表找寻两列数据的关系
count = pd.crosstab(data['week'], data['posi_neg'])
但是我们看到count只是每个星期日子的好坏天数,并没有得到比例,该怎么去做?
- 对于每个星期一等的总天数求和,运用除法运算求出比例
# 算数运算,先求和
sum = count.sum(axis=1)
# 进行相除操作,得出比例
pro = count.div(sum, axis=0)
查看效果
使用plot画出这个比例,使用stacked的柱状图
pro.plot(kind='bar', stacked=True)
plt.show()
使用pivot_table(透视表)实现
使用透视表,刚才的过程更加简单
# 通过透视表,将整个过程变成更简单一些
data.pivot_table(['posi_neg'], index='week')
总结
交叉表和透视表的区别:
交叉表cross_tab:功能简单,适合处理简单任务
透视表pivot_table:功能强大,适合复杂任务
如果你的目标是复杂的数值汇总,请使用
pivot_table;如果你的目标是快速查看分类数据的频数或比例分布,crosstab会更高效、更直观

