替换操作
替换操作可以同步作用于 Series 和 DataFrame 中。替换方法为 replace。替换操作包括单值替换和多指替换两种方式:
单值替换
- 普通替换: 替换所有符合要求的元素,
to_replace=15, value='e'
- 按列指定单值替换:
to_replace={列标签: 替换值}, value='value'
- 普通替换: 替换所有符合要求的元素,
多值替换
- 列表替换:替换列表中所有元素,
to_replace=[], value=[]
- 字典替换(推荐),
to_replace={to_replace: value, to_replace: value}
- 列表替换:替换列表中所有元素,
首先,创建一个 5 行 6 列的数据:
1 | df = DataFrame(np.random.randint(0, 100, size=(5, 6))) |
创建出来的数据为:
1 | 0 1 2 3 4 5 |
单值替换
普通替换
将列表中的 4 替换成 four,就是:
1 | df.replace(to_replace=4, value='four') |
替换完成的结果为:
1 | 0 1 2 3 4 5 |
有一点要注意,如果新值的数据类型发生变化,那么整个列的数据类型都会改变。比如,查看一下替换后的数据的列信息:
1 | df.replace(to_replace=4, value='four').info() |
我们看到,第二列的数据类型成了 object,也就是字符串:
1 | <class 'pandas.core.frame.DataFrame'> |
指定列替换
如果在数据表中有多个我们要替换的元素,比如 67 出现了两次。如果我们直接使用上面的方法
1 | df.replace(to_replace=67, value=666) |
两个 67 都会被替换成 666:
1 | 0 1 2 3 4 5 |
如果我们只想替换某一列中的 67,可以通过字典指定列替换。其中,字典的键为要替换的数据所在列,值为要替换的数据:
1 | df.replace(to_replace={-1: 67}, value=777) # 列索引也可以写成5,一样的 |
这样只有最后一列中的 67 被修改了,第一列的 67 仍然存在:
1 | 0 1 2 3 4 5 |
多值替换
有时候,我们要替换很多数据。如果一个一个替换,就有些麻烦。这时,我们可以通过多值替换,批量完成替换操作。
列表替换
也就是把多个替换的旧值与新值分别以列表的形式传给 to_replace 和 value 参数,要注意旧值与新值要一一对应,比如:
1 | df.replace(to_replace=[1, 7], value=['one', 'seven']) |
数据中的 1 就替换成了 one,7 替换成了 seven:
1 | 0 1 2 3 4 5 |
字典替换(推荐)
列标替换虽然可以实现多值替换,但是看起来并不直观,可读性不高。我们可以将旧值与新值以字典的键值对形式,传给 to_replace:
1 | df.replace(to_replace={15: 'piu', 95: 'pia'}) |
同样实现了多值替换:
1 | 0 1 2 3 4 5 |
映射操作
概念:创建一个映射关系列表,把 values 元素和一个特定的标签或者字符串绑定(给一个元素值提供不同的表现形式)。
map 只是 Serise 的函数,只能由 Series 调用。
创建一个 df,两列分别是姓名和薪资,然后给其名字起对应的英文名:
1 | dic = { |
创建出来的数据为:
1 | name salary |
然后我们用字典创建一个映射关系表,制定了映射的对照关系。通过这个映射关系,就可以实现映射:
1 | map_dic = { |
实际上,map 就是将原数据中的内容,用字典的键对应的值替换掉了:
1 | name salary nickname |
运算工具
Series 的 map 方法
需求:还是使用上面的数据,假设超过 3000 部分的钱需要缴纳 50% 的税,计算每个人的税后薪资。
我们可以通过定义一个计算税后薪资的函数,然后使用 map 方法获取税后薪水数据:
1 | def after_tax(salary): |
得到的新的一列 after_tax 就是税后的薪水了:
1 | name salary nickname after_tax |
DataFrame 中的 apply 操作
apply 是 df 的中运算工具,将运算作用到 df 的行或者列中。
首先创建一个 5 行 3 列的 DataFrame:
1 | df = DataFrame(np.random.randint(0, 100, size=(5, 3))) |
得到的数据为:
1 | 0 1 2 |
使用 df 的 apply,即可对每一列进行指定的操作:
1 | def my_add(num): |
每一列的所有元素都增加了 10:
1 | 0 1 2 |
apply 方法将会把 df 中的每一行/列传入到运算函数中。我们可以对这一行/列数据进行四则运算,但是不能进行更复杂的运算操作。当然,因为是以行/列传入,我们可以对数据进行一些聚合运算。
DataFrame 的 applymap 方法
applymap 可以将某种运算作用到 df 的每一个元素中:
1 | def my_add(num): |
每个元素都增加了 10:
1 | 0 1 2 |
排序实现的随机抽样
有的时候,如果数据量很大的话,对它们全部进行运算是不现实的。这时,我们可以从中抽取一部分数据,降低运算量。
随机抽样的思路是先将原来的数据的顺序打乱,也就是进行随机排序,然后提取新数据中的前几行。
将数据打乱,我们可以用到 DataFrame 的两个方法:
take()
,可以根据指定的索引取样np.random.permutation(n)
,用来创建从 0 到 n - 1 顺序随机的数组
首先创建一些数据:
1 | df = DataFrame(np.random.randint(0, 100, size=(100, 3)), columns=['A', 'B', 'C']) |
然后,指定索引进行取样,比如取索引为 4、11、2、7 的几行数据:
1 | df.take(indices=[4, 11, 2, 7], axis=0) |
- indices 用来指定我们需要的数据的索引,注意这里只能用隐式索引
- axis 用来指定方向,与 drop 系列相同,0 为横向取样,1 为纵向取样
我们就成功拿到这些行的数据:
1 | A B C |
对列取样道理相同,只是把 axis 指定为 1 即可。注意不能用显式索引。就不举例子了。
不难想象,如果我们有一个随机的索引数组,通过 take 取样,即可实现对数据的重新排列。
怎么获得顺序随机的索引数组呢?可以使用 np.random
的 permutation
方法来生成:
1 | np.random.permutation(5) |
这样就成了一组随机顺序的索引数组:
1 | array([1, 3, 0, 2, 4]) |
因为我们的数据有 100 行,只需生成 100 个随机索引数组,然后用这个数组进行取样,即可得到随机顺序的数据:
1 | df.take(np.random.permutation(100)) |
我们就实现了对原数据的随机排序:
1 | A B C |
然后我们提取其中前几条,即实现了对原数据的随机取样,比如只提取前 10 条数据:
1 | df.take(np.random.permutation(100))[:10] |
就拿到了随机的 10 条数据,因为又运行了一次随机取样,所以数据跟上面的是不同的:
1 | A B C |
数据的分类分组处理
在 MySQL 中有一个分组操作,可以用来对某一类数据进行聚合运算等处理。类似地,DataFrame 数据也可以进行分组操作,使用的方法也是 groupby。
数据分类处理的核心:
groupby()
函数分组groups
属性查看分组情况
首先,创建数据:
1 | df = DataFrame({ |
创建出来的数据就是:
1 | color item price weight |
使用 group 对水果种类 item 进行分组:
1 | df.groupby('item') |
分组得到了对象:
1 | <pandas.core.groupby.DataFrameGroupBy object at 0x000001A2D3D741D0> |
我们可以通过 groups 方法查看 group 对象的信息:
1 | df.groupby('item').groups |
我们看到,总共分成了三组,按照水果名分配的。每个水果的索引也记录在里面。
1 | {'Apple': Int64Index([0, 5], dtype='int64'), |
我们可以对分组对象使用 mean 方法,计算平均值:
1 | df.groupby('item').mean() |
只有数值型数据才能进行这类聚合操作,所以我们只得到了每种水果价格和重量的平均值,颜色信息被舍弃掉了。水果种类成了每一行的显式索引:
1 | price weight |
如果我们只想获取价格信息,可以对结果的列进行索引取值:
1 | df.groupby('item').mean()['price'] |
得到了水果价格的平均值信息:
1 | item |
但是我们一般不这么计算分组的平均值。因为这样做,会对分组中说有的列都进行聚合运算(这里是求平均值),将会消耗很多的资源。
更推荐的方式是将分组数据先索引取值,然后再进行聚合求平均值:
1 | df.groupby('item')['price'].mean() |
结果还是一样的,只不过这样只会对价格列进行运算,极大节省了资源:
1 | item |
光拿到均价并不能让我们满足,我们还需要将这些均值合并到原来的数据表中。我们可以通过映射的方法来实现:
1 | mean_price = df.groupby('item')['price'].mean() |
映射需要用到映射关系表,一般是个字典。这个字典的键就是数据表中的需要关联的数据,值就是要映射成为的值。通过 Series 的 to_dict 方法,刚好可以生成这样的对应关系。于是,平均值就成功添加到数据表中:
1 | color item price weight mean_price |
同样地,如果我们想要把每种颜色的水果的平均重量放到数据表中,实现分组聚合操作:
1 | mean_weight = df.groupby('color')['weight'].mean() |
合并后的数据为:
1 | color item price weight mean_price mean_weight |
高级数据聚合
使用 groupby 分组后,也可以使用 transform 和 apply 提供自定义函数实现更多的运算
df.groupby('item')['price'].sum()
<===>df.groupby('item')['price'].apply(sum)
- transform 和 apply 都会进行运算,在 transform 或者 apply 中传入函数即可
- transform 和 apply 也可以传入一个 lambda 表达式
仍旧使用上面的数据,将每一种水果的价格提升平均值的 15 个百分点,将提升后的价格汇总到源数据中:
1 | def add_price(num): |
得到的结果为:
1 | color item price weight mean_price mean_weight new_price |
我们也可以使用 transform 方法达成同样的任务:
1 | df.groupby('item')['price'].transform(lambda num: num + num.mean()) |
这里我用匿名函数代替了原来的函数,都是一样的,结果为:
1 | 0 7.00 |
如需和并到原数据中,直接合并即可。
数据加载
文本数据读取
read_csv 可以用来读取文本文件中的数据。文本文件一般以 csv 或 txt 为后缀。
比如,读取 type-.txt
文件中的数据:
1 | data = pd.read_csv('data/type-.txt') |
读取来的数据为:
1 | 你好-我好-他也好 |
很显然,这是一个两行一列的数据。但是我们希望它以 -
分隔,且第一行不要是表头,从而形成一个三行三列的数据。我们只需要指定分隔符 sep 为 -
,表头 header 为 None 即可:
1 | data = pd.read_csv('data/type-.txt', sep='-', header=None) |
成功获得了三行三列的数据:
1 | 0 1 2 |
我们还可以通过 names 参数来指定列索引:
1 | data = pd.read_csv('data/type-.txt', sep='-', header=None, names=['lala', 'haha', 'papa']) |
生成的列中就都有了索引:
1 | lala haha papa |
数据库数据读取
连接数据库获取数据
Pandas 可以直接使用关系型数据库(如:MySQL、Oracle、SQLite,等)的连接导入数据。
例如,导入 SQLite 中的数据:
1 | import sqlite3 |
顺利读取到 SQLite 中的数据:
1 | index Date/Time Temp (C) Dew Point Temp (C) Rel Hum (%) Wind Spd (km/h) Visibility (km) Stn Press (kPa) Weather |
类似地,我们也可以使用 pymysql 读取 MySQL 中的数据:
1 | import pymysql |
成功读取到数据:
1 | sid sname gender class_id |
将数据存储到数据库
使用 DataFrame 的 to_sql 命令可以将之前用到的存放商品信息的 df 中的数据值写入存储到数据库中:
1 | conn = sqlite3.connect('data/weather_2012.sqlite') |
查看数据库中的数据:
1 | pd.read_sql('select * from goods_data', conn) |
发现数据已经成功写入到数据库中:
1 | index color item price weight mean_price mean_weight new_price |
透视表
透视表是一种可以对数据动态排布并且分类汇总的表格格式。或许大多数人都在 Excel 使用过数据透视表,也体会到它的强大功能,而在 pandas 中它被称作 pivot_table。
透视表的优点有很多:
- 灵活性高,可以随意定制你的分析计算要求
- 脉络清晰易于理解数据
- 操作性强,报表神器
pivot_table 有四个最重要的参数:index、values、columns、aggfunc,接下来我们逐个介绍一下。
首先读取文件中的数据:
1 | df = pd.read_csv('./data/透视表-篮球赛.csv', encoding='utf8', engine='python') |
因为文件中有中文,所以需要指定编码。文件路径中出现中文,可能会报错,这时候可以尝试指定 engine 为 python 解决。
读取到的数据为:
1 | 对手 胜负 主客场 命中 投篮数 投篮命中率 3分命中率 篮板 助攻 得分 |
index 参数:分类汇总的分类条件。
每个 pivot_table 必须拥有一个 index。如果想查看哈登对阵每个队伍的得分则需要对每一个队进行分类并计算其各类得分的平均值。
首先,需要拿到哈登对阵同一对手在不同主客场下的数据,分类条件为对手和主客场:
1 | df.pivot_table(index=['对手', '主客场']) |
同 groupby 分组类似,除了 index,我们只拿到了数值型的数据:
1 |
|
values 参数:需要对计算的数据进行筛选。
如果我们只需要哈登在主客场和不同胜负情况下的得分、篮板与助攻三项数据:
1 | df.pivot_table(index=['主客场', '胜负'], values=['得分', '篮板', '助攻']) |
我们就得到了简化的数据:
1 | 助攻 得分 篮板 |
Aggfunc 参数:设置我们对数据聚合时进行的函数操作
当我们未设置 aggfunc 时,它默认 aggfunc='mean'
计算均值。也就是说,我们上面的数据其实是平均值。
如果我们想获得 James Harden 在主客场和不同胜负情况下的总得分、总篮板、总助攻时:
1 | df.pivot_table(index=['主客场', '胜负'], values=['得分', '篮板', '助攻'], aggfunc='sum') |
我们就得到了总合的数据:
1 | 助攻 得分 篮板 |
更高端的写法是,我们可以给每一列指定一个聚合函数:
1 | df.pivot_table(index=['主客场', '胜负'], aggfunc={'得分': 'sum', '篮板': 'mean', '助攻': 'std'}) |
结果就是:
1 | 助攻 得分 篮板 |
columns:可以设置列层次字段
columns 参数用来对 values 字段进行分类。
通过前面的学习,我们很容易可以获取所有队主客场的总得分:
1 | df.pivot_table(index='主客场', values='得分', aggfunc='sum') |
数据也成功拿到了:
1 | 得分 |
但是这个数据太笼统,我们希望看到的是每个队主客场的总得分(在总得分的基础上又进行对手的分类),这就可以指定 columns 参数了:
1 | df.pivot_table(index='主客场', values='得分', aggfunc='sum', columns='对手') |
我们就得到了每个队伍主客场得分的数据:
1 | 对手 76人 勇士 国王 太阳 小牛 尼克斯 开拓者 掘金 步行者 湖人 灰熊 爵士 猛龙 篮网 老鹰 骑士 鹈鹕 黄蜂 |
如果不喜欢这里的 NaN, 我们还可以通过指定 fill_value 参数的方式将其替换:
1 | df.pivot_table(index='主客场', values='得分', columns='对手', aggfunc='sum', fill_value=0) |
这样得到的数据就没有 NaN 了:
1 | 对手 76人 勇士 国王 太阳 小牛 尼克斯 开拓者 掘金 步行者 湖人 灰熊 爵士 猛龙 篮网 老鹰 骑士 鹈鹕 黄蜂 |
交叉表
交叉表是一种用于计算分组的特殊透视图,用于对数据进行汇总。需要注意的是,crosstab 是 Pandas 的方法,而非 DataFrame 的。
交叉表就是一种特殊的透视表,只不过将聚合函数指定为了 count。交叉表能实现的,透视表都可以实现。
pd.crosstab(index, colums)
- index:分组数据,交叉表的行索引
- columns:交叉表的列索引
第一件事,当然是构造数据:
1 | df = DataFrame({ |
数据是这样的:
1 | age height sex smoke |
使用交叉表求出各个性别抽烟的人数:
1 | pd.crosstab(df.sex, df.smoke) |
得到的结果为:
1 | smoke False True |
求出各个年龄段抽烟人情况:
1 | pd.crosstab(df.smoke, df.age) |
得到的结果就是:
1 | age 15 17 22 23 24 25 31 35 57 |