3. Pandas 25 式
Kevin Markham,数据科学讲师,2002 年,毕业于范德堡大学,计算机工程学士,2014 年,创建了 Data School,在线教授 Python 数据科学课程,他的课程主要包括 Pandas、Scikit-learn、Kaggle 竞赛数据科学、机器学习、自然语言处理等内容,迄今为止,浏览量在油管上已经超过 500 万次。
Kevin 还是 PyCon 培训讲师,主要培训课程如下:
PyCon 2016,用 Scikit-learn 机器学习技术处理文本
PyCon 2018,如何用 Pandas 更好(或更糟)地实现数据科学
PyCon 2019,Pandas 数据科学最佳实践
本文基于 Kevin 于 2019 年 7 月推出的最新视频教程,汇总了他 5 年来最喜欢的 25 个 pandas 操作技巧,希望大家喜欢。
目录
查看 pandas 及其支持项的版本
创建 DataFrame
重命名列
反转行序
反转列序
按数据类型选择列
把字符串转换为数值
优化 DataFrame 大小
用多个文件建立 DataFrame ~ 按行
用多个文件建立 DataFrame ~ 按列
从剪贴板创建 DataFrame
把 DataFrame 分割为两个随机子集
根据多个类别筛选 DataFrame
根据最大的类别筛选 DataFrame
操控缺失值
把字符串分割为多列
把 Series 里的列表转换为 DataFrame
用多个函数聚合
用一个 DataFrame 合并聚合的输出结果
选择行与列
重塑多重索引 Series
创建透视表
把连续型数据转换为类别型数据
改变显示选项
设置 DataFrame 样式
彩蛋:预览 DataFrame
文末有 Jupyter Notebook 下载,正文先上图。
3.1. 使用的数据集
原文的数据集是 bit.ly 短网址的,我这里在读取时出问题,不稳定,就帮大家下载下来,统一放到了 data 目录里。
drinks = pd.read_csv('data/drinks.csv')
movies = pd.read_csv('data/imdb_1000.csv')
orders = pd.read_csv('data/chipotle.tsv', sep='\t')
orders['item_price'] = orders.item_price.str.replace('$', '').astype('float')
stocks = pd.read_csv('data/stocks.csv', parse_dates=['Date'])
titanic = pd.read_csv('data/titanic_train.csv')
ufo = pd.read_csv('data/ufo.csv', parse_dates=['Time'])
本文中采用让数据集主要为常见的酒水饮料、IMDB 电影、泰坦尼克号、飞碟目击等数据集。
这里需要注意的是:
1) pd.read_csv('data/chipotle.tsv', sep='\t')
里的 chipotle.tsv,是用 tab 作为分隔符的,所以要增加参数 sep=\t
;
2) orders.item_price.str.replace('$', '').astype('float')
,item_price 列是带 $ 的文本,要用 .str.replace('$', '').astype('float')
去掉 $,再把该列数据类型改为 float
;
3)ufo.csv
里的 Time 列,要用 parse_dates=['Time'])
,解析日期。
3.2. 查看 pandas 及其支持项的版本
使用 pd.__version__
查看 pandas 的版本。
查看所有 pandas 的支持项版本,使用 show_versions
函数。比如,查看 Python、pandas、Numpy、matplotlib 等支持项的版本。
3.3. 创建 DataFrame
创建 DataFrame 的方式有很多,比如,可以把字典传递给 DataFrame 构建器,字典的 Key 是列名,字典的 Value 为列表,是 DataFrame 的列的值。
如果 DataFrame 的数据较多,用字典的方式就不合适了,需要输入的东西太多。这时,可以用 Numpy 的 random.rand()
函数,设定行数与列数,然后把值传递给 DataFrame 构建器。
这样就可以生成 DataFrame 了,但如果要用非数字形式的列名,需要强制把字符串转换为列表, 再把这个列表传给 columns
参数。
这里要注意的是,字符串里的字符数量必须与 DataFrame 的列数一致。
3.4. 重命名列
用点(.
)选择 pandas 里的列写起来比较容易,但列名里有空格,就没法这样操作了。
rename()
方法改列名是最灵活的方式,它的参数是字典,字典的 Key 是原列名,值是新列名,还可以指定轴向(axis)。
这种方式的优点是可以重命名任意数量的列,一列、多列、所有列都可以。
还有一种简单的方式可以一次性重命名所有列,即,直接为列的属性赋值。
只想替换列名里的空格,还有更简单的操作,直接用 str.replace
方法,不必把所有的列名都敲一遍。
以上这三种方式都可以更改列名。
用 add_prefix
与 add_suffix
函数可以为所有列名添加前缀或后缀。
3.5. 反转列序
反转 drinks
表的顺序。
这个数据集按国家列出了酒水平均消耗量,如果想反转列序该怎么办?
最直接的方式是把 ::-1
传递给 loc
访问器,与 Python 里反转列表的切片法一样。
如果想让索引从 0 到 1,用 reset_index()
方法,并用 drop
关键字去掉原有索引。
这样,行序就已经反转过来了,索引也重置为默认索引。
3.6. 反转列序
与反转行序类似,还可以用 loc
从左到右反转列序。
逗号前面的分号表示选择所有行,逗号后面的 ::-1
表示反转列,这样一来,country 列就跑到最右边去了。
3.7. 按数据类型选择列
首先,查看一下 drinks 的数据类型:
选择所有数值型的列,用 select_dtypes()
方法。
同样的方法,还可以选择所有字符型的列。
同理,还可以用 datetime
选择日期型的列。
传递列表即可选择多种类型的列。
还可以使用 exclude
关键字排除指定的数据类型。
3.8. 把字符串转换为数值
再创建一个新的 DataFrame 示例。
这个 DataFrame 里的数字其实是以字符串形式保存的,因此,列类型是 object
。
要想执行数学计算,要先把这些列的数据类型转换为数值型,下面的代码用 astype()
方法把前两列的数据类型转化为 float
。
用这种方式转换第三列会出错,因为这列里包含一个代表 0 的下划线,pandas 无法自动判断这个下划线。
为了解决这个问题,可以使用 to_numeric()
函数来处理第三列,让 pandas 把任意无效输入转为 NaN
。
NaN
代表的是 0,可以用 fillna()
方法填充。
一行代码就可以解决这个问题,现在所有列的值都转成 float
了。
3.9. 优化 DataFrame 对内存的占用
pandas 的 DataFrame 设计的目标是把数据存到内存里,有时要缩减 DataFrame 的大小,减少对内存的占用。
下面显示了 drinks 占用的内存。
这里显示 drinks 使用了 30.5 KB 内存。
大型 DataFrame 会影响计算性能,甚至导致 DataFrame 读入内存失败,下面介绍简单几步,即可在读取 DataFrame 时减少内存占用。
第一步是只读取切实所需的列,这里需要指定 usecols
参数。
只选择两列以后,DataFrame 对内存的占用减少到 13.7 KB。
第二步是把包含类别型数据的 object 列转换为 Category 数据类型,通过指定 dtype
参数实现。
把 continent 列改为 category 数据类型后,DataFrame 对内存的占用进一步缩减到 2.4 KB。
注意:类别数量相对于行数较少时,category 数据类型对对内存占用的减少会比较有限。
3.10. 用多个文件建立 DataFrame ~ 按行
本段介绍怎样把分散于多个文件的数据集读取为一个 DataFrame。
比如,有多个 stock 文件,每个 CSV 文件里只存储一天的数据。
下面是三天的股票数据:
把每个 CSV 文件读取成 DataFrame,合并后,再删除导入的原始 DataFrame,但这种方式占用内存太多,而且要写很多代码。
使用 Python 内置的 glob
更方便。
把文件名规则传递给 glob()
,这里包括通配符,即可返回包含所有合规文件名的列表。
本例里,glob
会查找 data 子目录里所有以 stocks 开头的 CSV 文件。
glob
返回的是无序文件名,要用 Python 内置的 sorted()
函数排序列表。
调用 read_csv()
函数读取生成器表达式里的每个文件,把读取结果传递给 concat()
函数,然后合并为一个 DataFrame。
注:原文里用的是 stock_files = sorted(glob('data/stocks*.csv'))
,译文里没用 stocks*
,用的是 stocks?
,这是因为 data 目录里还有一个叫 stocks.csv 的文件,如果用 *,会读取出 4 个文件,而不是原文中的 3 个文件。
生成的 DataFrame 索引有重复值,见 “0、1、2”。为避免这种情况,要在 concat()
函数里用忽略旧索引、重置新索引的参数,ignore_index = True
。
3.11. 用多个文件建立 DataFrame ~ 按列
上个技巧按行合并数据集,但是如果多个文件包含不同的列,该怎么办?
本例将 drinks 数据集分为了两个 CSV 文件,每个文件都包含 3 列。
与上例一样,还是使用 glob()
。
这里要让 concat()
函数按列合并,axis='columns
。
现在 drinks 有 6 列啦!
3.12. 从剪贴板创建 DataFrame
想快速把 Excel 或别的表格软件里存储的数据读取为 DataFrame,用 read_clipboard()
函数。
打开要复制的 Excel 文件,选取内容,复制。
与 read_csv()
函数类似,read_clipboard()
会自动检测列名与每列的数据类型。
真不错! pandas 自动把第一列当设置成索引了。
注意:因为不能复用、重现,不推荐在正式代码里使用 read_clipboard()
函数。
3.13. 把 DataFrame 分割为两个随机子集
把 DataFrame 分为两个随机子集,一个占 75% 的数据量,另一个是剩下的 25%。
以 Movies 为例,该数据有 979 条记录。
使用 sample()
方法随机选择 75% 的记录,并将之赋值给 moives_1。
使用 drop()
方法删掉 movies 里所有 movies_1,并将之赋值给 movies_2。
两个 DataFrame 的行数之和与 movies 一致。
movies_1 与 movies_2 里的每个索引值都来自于 movies,而且互不重复。
注意:如果索引值有重复、不唯一,这种方式会失效。
3.14. 根据多个类别筛选 DataFrame
预览 movies。
查看 genre(电影类型)列。
要是想筛选 Action(动作片)、Drama(剧情片)、Western(西部片),可以用 or
的操作符实现多条件筛选。
不过,用 isin()
方法筛选会更清晰,只要传递电影类型的列表就可以了。
如果想反选,可在条件前添加一个波浪符(tilde ~)。
3.15. 根据最大的类别筛选 DataFrame
筛选电影类别里(genre)数量最多的三类电影。
先用 value_counts()
统计各类电影的数量,把统计结果赋值给 counts
,这个结果是 Series。
使用 Series 的 nlargest
方法,可以轻松选出 Series 里最大的三个值。
这里所需的只是这个 Series 的 index。
把这个 index 传递给 isin()
。
最终,这个 DataFrame 里就只剩下了剧情片、喜剧片与动作片。
3.16. 处理缺失值
本例使用目击 UFO 数据集。
可以看到,这个数据集里有缺失值。
要查看每列有多少缺失值,可以使用 isna()
方法,然后使用 sum()
函数。
isna()
生成一个由 True
与 False
构成的 DataFrame,sum()
把 True
转换为 1, 把 False
转换为 0。
还可以用 mean()
函数,计算缺失值占比。
用 dropna()
删除列里的所有缺失值。
只想删除列中缺失值高于 10% 的缺失值,可以设置 dropna()
里的阈值,即 threshold
.
3.17. 把字符串分割为多列
创建一个 DataFrame 示例。
把姓名列分为姓与名两列,用 str.split()
方法,按空格分割,并用 expand
关键字,生成一个新的 DataFrame。
通过赋值语句,把这两列添加到原 DataFrame。
如果想分割字符串,但只想保留分割结果的一列,该怎么操作?
要是只想保留城市列,可以选择只把城市加到 DataFrame 里。
3.18. 把 Series 里的列表转换为 DataFrame
创建一个 DataFrame 示例。
这里包含了两列,第二列包含的是 Python 整数列表。
要把第二列转为 DataFrame,在第二列上使用 apply()
方法,并把结果传递给 Series 构建器。
用 concat()
函数,把原 DataFrame 与新 DataFrame 组合在一起。
3.19. 用多个函数聚合
先看一下 Chipotle 连锁餐馆的 DataFrame。
每个订单都有订单号(order_id),每个订单有多行。要统计每个订单的金额,需要先根据每个 order_id 汇总每个订单里各个产品(item_price)的金额。下面的例子列出了订单号为 1 的总价。
计算每单的总价,要按 order_id
进行 groupby()
分组,再按 item_price
计算每组的总价。
有时,要用多个聚合函数,不一定只是 sum()
一个函数。这时,要用 agg()
方法,把多个聚合函数的列表作为该方法的参数。
上列就算出了每个订单的总价与订单里的产品数量。
3.20. 用一个 DataFrame 合并聚合的输出结果
本例用的还是 orders。
如果想新增一列,为每行列出订单的总价,要怎么操作?上面介绍过用 sum()
计算总价。
sum()
是聚合函数,该函数返回结果的行数(1834行)比原始数据的行数(4622行)少。
要解决这个问题得用 transform()
方法,这个方法执行同样的计算,但返回与原始数据行数一样的输出结果,本例中为 4622 行。
接下来,为 DataFrame 新增一列,total_price
。
如上所示,每一行都列出了对应的订单总价。
这样一来,计算每行产品占订单总价的百分比就易如反掌了。
3.21. 选择行与列
本例使用大家都看腻了的泰坦尼克数据集。
这个数据集包括了泰坦尼克乘客的基本信息以及是否逃生的数据。
用 describe()
方法,可以得到该数据集的基本统计数据。
这个结果集显示的数据很多,但不一定都是你需要的,可能只需要其中几行。
还可以只选择部分列。
3.22. 重塑多重索引 Series
泰坦尼克数据集里有一列标注了幸存(Survived)状态,值用 0、1 代表。计算该列的平均值可以计算整体幸存率。
按性别(Sex)统计男女的幸存率,需要使用 groupby()
。
要按性别与舱型(Pclass)统计幸存率,就要按性别与舱型进行 groupby()
。
上面显示了不同性别,不同舱型的幸存率,输出结果是一个多重索引的序列(Series),这种形式与实际数据相比多了多重索引。
这种表现形式不利于阅读,也不方便实现数据交互,用 unstack()
把多重索引转换为 DataFrame 更方便。
这个 DataFrame 包含的数据与多重索引序列一模一样,只是可以用大家更熟悉的 DataFrame 方法进行操控。
3.23. 创建透视表
经常输出类似上例的 DataFrame,pivot_table()
方法更方便。
使用透视表,可以直接指定索引、数据列、值与聚合函数。
设置 margins=True
,即可为透视表添加行与列的汇总。
此表显示了整体幸存率,及按性别与舱型划分的幸存率。
把聚合函数 mean
改为 count
,就可以生成交叉表。
这里显示了每个类别的记录数。
3.24. 把连续型数据转换为类型数据
下面看一下泰坦尼克数据集的年龄(Age)列。
这一列是连续型数据,如果想把它转换为类别型数据怎么办?
这里可以用 cut
函数把年龄划分为儿童、青年、成人三个年龄段。
这段代码为不同分箱提供了标签,年龄在 0-18 岁的为儿童,18-25 岁的为青年,25-99 岁的为成人。
注意:现在数据已经是类别型了,类别型数据会自动排序。
3.25. 改变显示选项
接下来还是看泰坦尼克数据集。
年龄列有 1 位小数,票价列有 4 位小数,如何将这两列显示的小数位数标准化?
用以下代码让这两列只显示 2 位小数。
第一个参数是要设置的选项名称,第二个参数是 Python 的字符串格式。
现在年龄与票价列为 2 位小数了。
注意:这种操作不改变底层数据,只改变数据的显示形式。
还可以用以下代码重置数据显示选项。
pd.reset_option('display.float_format')
注意:使用同样的方式,还可以设置更多选项。
3.26. 设置 DataFrame 样式
上面的技巧适用于调整整个 Jupyter Notebook 的显示内容。
不过,要想为某个 DataFrame 设定指定的样式,pandas 还提供了更灵活的方式。
下面看一下 stocks。
创建样式字符字典,指定每列使用的格式。
把这个字典传递给 DataFrame 的 style.format()
方法。
注意:日期是月-日-年的格式,闭市价有美元符,交易量有千分号。
接下来用链式方法实现更多样式。
可以看到,这个表隐藏了索引,闭市价最小值用红色显示,最大值用浅绿色显示。
再看一下背景色渐变的样式。
交易量(Volume)列现在按不同深浅的蓝色显示,一眼就能看出来数据的大小。
下面看最后一个例子。
本例的 DataFrame 加上了标题,交易量列使用了迷你条形图。
注意:Pandas 还支持更多 DataFrame 样式选项,详见 pandas 官方文档。
3.27. 彩蛋:预览 DataFrame
假如刚拿到一个数据集,想快速了解该数据集,又不想费劲折腾怎么办?这里介绍一个独立的支持库,pandas_profiling,可以快速预览数据集。
第一步,安装, pip install pandas-profiling
第二步,导入,import pandas_profiling
本例简单介绍一下 ProfileReport()
函数,这个函数支持任意 DataFrame,并生成交互式 HTML 数据报告:
第一部分是纵览数据集,还会列出数据一些可能存在的问题;
第二部分汇总每列数据,点击 toggle details 查看更多信息;
第三部分显示列之间的关联热力图;
第四部分显示数据集的前几条数据。