Pandas (上)
前言
本篇鸣谢 马川-燕大 的增删整理, 王圣元 ——原创文章,与原文不同之处包含我的学习记录。
匹配Jupyter Notebook的ipynb文档链接下载地址如下
0 引言
Pandas 是 Python 为解决数据分析而创建的,详情看官网 https://pandas.pydata.org/。 在使用 pandas 之前,需要引进它,语法如下:
import pandas
这样就可以用 pandas 里面所有的内置方法 (build-in methods) 了,比如创建一维的 Series 和二维的 DataFrame。
pandas.Series()
pandas.DataFrame()
但是每次写 pandas 字数有点多,通常给 pandas 起个别名 pd,用以下语法,这样所有出现 pandas 的地方都可以用 pd 替代。
|
|
Pandas 里面的数据结构是「多维数据表」,学习它可以类比这 NumPy 里的「多维数组」。1/2 维的「多维数据表」分别叫做 Series (系列)和 DataFrame (数据帧),与1/2 维的「多维数组」的类比关系如下。
由于「系列」、「数据帧」这些直译过来的中文名词听起来有些奇怪,在本帖还是直接用 Series和 DataFrame。
对比 NumPy (np) 和 Pandas (pd) 每个维度下的数据结构,不难看出
pd 多维数据表 = np 多维数组 + 描述
其中
-
Series = 1darray + index
-
DataFrame = 2darray + index + columns
每个维度上的「索引」使得「多维数据表」比「多维数组」涵盖更多的信息,如下图,左边的 2d array 仅仅储存了一组数值 (具体代表什么意思却不知道),而右边的 DataFrame 一看就知道这是平安银行和茅台从 2018-1-3 到 2019-1-3 的价格。
和学习 numpy 一样,学习 pandas 还是遵循的 Python 里「万物皆对象」的原则,既然把数据表当对象,就得按着数据表的创建、数据表的存载、数据表的获取、数据表的合并和连接、数据表的重塑和透视、和数据表的分组和整合来盘一盘 Pandas。
提纲:
由于篇幅原因,Pandas 系列分两贴,上贴讲前三节的内容,下帖讲后三节的内容。
1 数据表的创建
数据表有两大类型
-
Series: 一维数据,类似于 python 中的基本数据的 list 或 NumPy 中的 1D array。Pandas 里最基本的数据结构
-
DataFrame: 二维数据,类似于 R 中的 data.frame 或 Matlab 中的 Tables。DataFrame 是 Series 的容器
知识点
最常见的数据类型是二维的 DataFrame,其中
-
每行代表一个示例 (instance)
-
每列代表一个特征 (feature)
DataFrame 可理解成是 Series 的容器,每一列都是一个 Series,或者 Series 是只有一列的 DataFrame。
接下来用代码来创建 pandas 数据表:
一维 Series
创建 Series 只需用下面一行代码
pd.Series( x, index=idx )
其中 x 可以是
-
列表 (list)
-
numpy 数组 (ndarray)
-
字典 (dict)
回顾Python编程基础中函数的定义,那么
-
x 是位置参数
-
index 是默认参数,默认值为 idx = range(0, len(x))
用列表
|
|
0 27.20
1 27.65
2 27.70
3 28.00
dtype: float64
打印出来并不仅仅是列表里面的浮点数,每个浮点数前面还有一个索引,在本例中是 0, 1, 2, 3。
因此在创建 Series 时,如果不显性设定 index,那么 Python 给定一个默认从 0 到 N-1 的值,其中 N 是 x 的长度。
Series s 也是一个对象,用 dir(s) 可看出关于 Series 所有的属性和内置函数,其中最重要的是
-
用 s.values 打印 s 中的元素
-
用 s.index 打印 s 中的元素对应的索引
|
|
array([27.2 , 27.65, 27.7 , 28. ])
|
|
RangeIndex(start=0, stop=4, step=1)
不难发现,以上创建的 Series 和 numpy 数组比多了「索引」,但这种 0,1,2,3 的索引是在没有什么描述意义。实际上定义的 s 是海底捞在 2019 年 4 月 1 日到 2019 年 4 月 4 日的股价,那么用日期来当索引是不是更好些?
|
|
2019-04-01 27.20
2019-04-02 27.65
2019-04-03 27.70
2019-04-04 28.00
Freq: D, dtype: float64
pandas.date_range(start=None, end=None, periods=None, freq=‘D’, tz=None, normalize=False, name=None, closed=None, **kwargs)
该函数主要用于生成一个固定频率的时间索引,在调用构造方法时,必须指定start、end、periods中的两个参数值,否则报错。
主要参数说明:
-
periods:固定时期,取值为整数或None
-
freq:日期偏移量,取值为string或DateOffset,默认为’D’
-
normalize:若参数为True表示将start、end参数值正则化到午夜时间戳
-
name:生成时间索引对象的名称,取值为string或None
-
closed:可以理解成在closed=None情况下返回的结果中,若closed=‘left’表示在返回的结果基础上,再取左开右闭的结果,若closed=‘right’表示在返回的结果基础上,再取做闭右开的结果
显然,s2 比 s 包含的信息更多,这是 s2 的索引是一组日期对象,数据类型是 datetime64,频率是 D (天)。
|
|
DatetimeIndex(['2019-04-01', '2019-04-02', '2019-04-03', '2019-04-04'], dtype='datetime64[ns]', freq='D')
甚至还可以给 s2 命名,就叫海底捞股价如何?
|
|
2019-04-01 27.20
2019-04-02 27.65
2019-04-03 27.70
2019-04-04 28.00
Freq: D, Name: 海底捞股价, dtype: float64
用 numpy 数组
除了用列表,还可以用 numpy 数组来生成 Series。在下例中,加入缺失值 np.nan,并分析一下 Series 中另外 5 个属性或内置函数的用法:
-
len: s 里的元素个数
-
shape: s 的形状 (用元组表示)
-
count: s 里不含 nan 的元素个数
-
unique: 返回 s 里不重复的元素
-
value_counts: 统计 s 里非 nan 元素的出现次数
对照上面函数的用法,下面的输出一看就懂了吧。
空值的产生只有np.nan()
总结一下:
np.nan不是一个“空”对象,用 i is None判断是False; 对某个值是否为空值进行判断,只能用np.isnan(i)函数,万万不可用 i == np.nan()来做,否则你会死的很惨的,因为空值并不能用判断相等的“==”正确识别(上例前两条);
np.nan非空对象,其类型为基本数据类型float(是不是很神奇,我也不知道为什么要这样设计)
|
|
The length is 6
The shape is (6,)
The count is 5
|
|
array([27.2 , 27.65, 27.7 , 28. , nan])
|
|
28.00 2
27.70 1
27.65 1
27.20 1
dtype: int64
用字典
创建 Series 还可以用字典。字典的「键值对」的「键」自动变成了 Series 的索引 (index),而「值」自动变成了Series 的值 (values)。代码如下 (下列用 name 参数来对 s3 命名)
|
|
股票代号
BABA 187.07
PDD 21.83
JD 30.79
BIDU 184.77
Name: 中概股, dtype: float64
给 s3 起名中概股是因为阿里巴巴 (BABA)、拼多多 (PDD)、京东 (JD) 和百度 (BIDU) 都是中国公司但在美国上市的。此外还可以给 index 命名为 ‘股票代号’。
现在假设这里的股票代号为
|
|
FB NaN
BABA 187.07
PDD 21.83
JD 30.79
Name: 中概股, dtype: float64
代号里多加了脸书 (FB),而 sdata 字典中没有 FB 这个键,因此生成的 s4 在 FB 索引下对应的值为 NaN。再者,代号里没有百度 (BIDU),因此 s4 里面没有 BIDU 对应的值 (即便 sdata 里面有)。
当两个 Series 进行某种操作时,比如相加,Python 会自动对齐不同 Series 的 index,如下面代码所示:
|
|
BABA 374.14
BIDU NaN
FB NaN
JD 61.58
PDD 43.66
Name: 中概股, dtype: float64
Series 是 Pandas 里面最基本的数据结构,但是对应每个索引只有一个元素 (比如一个日期对应一个股价),因此 Series 处理不了每个索引对应多个元素 (比如一个日期对应一个开盘价、收盘价、交易量等等)。而 DataFrame 可以解决这个问题。
二维 DataFrame
创建 DataFrame 只需用下面一行代码
pd.DataFrame( x, index=idx, columns=col )
其中 x 可以是
-
二维列表 (list)
-
二维 numpy 数组 (ndarray)
-
字典 (dict),其值是一维列表、numpy 数组或 Series
-
另外一个 DataFrame
回顾Python编程基础中函数的定义,那么
-
x 是位置参数
-
index 是默认参数,默认值为 idx = range(0, x.shape[0]),行索引
-
columns 是默认参数,默认值为 col = range(0, x.shape[1]),列索引
用列表或 numpy 数组
|
|
0 | 1 | 2 | |
---|---|---|---|
0 | 1 | 2 | 3 |
1 | 4 | 5 | 6 |
在创建 DataFrame 时,如果不显性设定 index 和 columns 时,那么Python 给它们默认值,其中
-
index = 0 到 r-1,r 是 x 的行数
-
colmns = 0 到 c-1,c 是 x 的列数
用对象为列表的字典
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
BABA | 电商 | 176.92 | 16175610 | 101550 |
JD | 电商 | 25.95 | 27113291 | 175336 |
AAPL | 科技 | 172.97 | 18913154 | 100000 |
MS | 金融 | 41.79 | 10132145 | 60348 |
GS | 金融 | 196.00 | 2626634 | 36600 |
WMT | 零售 | 99.55 | 8086946 | 2200000 |
字典的「键值对」的「键」自动变成了 DataFrame 的栏 (columns),而「值」自动变成了 DataFrame 的值 (values),而其索引 (index) 需要另外定义。
分别来看 df2 的 values, columns 和 index。
|
|
array([['电商', 176.92, 16175610, 101550],
['电商', 25.95, 27113291, 175336],
['科技', 172.97, 18913154, 100000],
['金融', 41.79, 10132145, 60348],
['金融', 196.0, 2626634, 36600],
['零售', 99.55, 8086946, 2200000]], dtype=object)
|
|
Index(['行业', '价格', '交易量', '雇员'], dtype='object')
|
|
Index(['BABA', 'JD', 'AAPL', 'MS', 'GS', 'WMT'], dtype='object', name='代号')
A 查看 DataFrame
这里可以从头或从尾部查看 DataFrame 的 n 行,分别用 df2.head(n) 和 df2.tail(n),如果没有设定 n,默认值为 5 行。
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
BABA | 电商 | 176.92 | 16175610 | 101550 |
JD | 电商 | 25.95 | 27113291 | 175336 |
AAPL | 科技 | 172.97 | 18913154 | 100000 |
MS | 金融 | 41.79 | 10132145 | 60348 |
GS | 金融 | 196.00 | 2626634 | 36600 |
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
MS | 金融 | 41.79 | 10132145 | 60348 |
GS | 金融 | 196.00 | 2626634 | 36600 |
WMT | 零售 | 99.55 | 8086946 | 2200000 |
B 统计 DataFrame
可以用 df2.describe() 还可以看看 DataFrame 每栏的统计数据。
|
|
价格 | 交易量 | 雇员 | |
---|---|---|---|
count | 6.000000 | 6.000000e+00 | 6.000000e+00 |
mean | 118.863333 | 1.384130e+07 | 4.456390e+05 |
std | 73.748714 | 8.717312e+06 | 8.607522e+05 |
min | 25.950000 | 2.626634e+06 | 3.660000e+04 |
25% | 56.230000 | 8.598246e+06 | 7.026100e+04 |
50% | 136.260000 | 1.315388e+07 | 1.007750e+05 |
75% | 175.932500 | 1.822877e+07 | 1.568895e+05 |
max | 196.000000 | 2.711329e+07 | 2.200000e+06 |
函数 describe() 只对「数值型变量」有用 (没有对「字符型变量」行业栏做统计),统计量分别包括个数、均值、标准差、最小值,25-50-75 百分数值,最大值。一般做数据分析第一步会用这个表大概看看
- 数据是否有缺失值 (每个栏下的 count 是否相等)?
* 数据是否有异常值 (最小值 min 和最大值 max 是否太极端)?
C 升维 DataFrame
用 MultiIndex.from_tuples() 还可以赋予 DataFrame 多层索引 (实际上增加了维度,多层索引的 DataFrame 实际上是三维数据)。
|
|
行业 | 价格 | 交易量 | 雇员 | ||
---|---|---|---|---|---|
中国公司 | BABA | 电商 | 176.92 | 16175610 | 101550 |
JD | 电商 | 25.95 | 27113291 | 175336 | |
美国公司 | AAPL | 科技 | 172.97 | 18913154 | 100000 |
MS | 金融 | 41.79 | 10132145 | 60348 | |
GS | 金融 | 196.00 | 2626634 | 36600 | |
WMT | 零售 | 99.55 | 8086946 | 2200000 |
在 MultiIndex.from_tuples() 中传递一个「元组的列表」,每个元组,比如 (‘中国公司’, ‘BABA’),第一个元素中国公司是第一层 index,第二个元素BABA是第二层 index。
2 数据表的存载
本节讲数据表的「保存」和「加载」,在 NumPy 一贴已经提到过,数据的存载没什么技术含量
-
保存只是为了下次再用处理好的 DataFrame
-
加载可以不用重新再定义 DataFrame
DataFrame 可以被保存为 Excel, csv, SQL 和 HDF5 格式,其语句一看就懂,用 to_数据格式,具体如下:
-
to_excel()
-
to_csv()
-
to_sql()
-
to_hdf()
如果要加载某种格式的数据到 DataFrame 里,用 read_数据格式,具体如下:
-
read_excel()
-
read_csv()
-
read_sql()
-
read_hdf()
这里只用 excel 和 csv 格式举例。
Excel 格式
用 pd.to_excel 函数将 DataFrame 保存为 .xlsx 格式,并保存到 ‘Sheet1’ 中,具体写法如下:
pd.to_excel( ‘文件名’,‘表名’ )
|
|
0 | 1 | 2 | |
---|---|---|---|
0 | 1 | 2 | 3 |
1 | 4 | 5 | 6 |
用 pd.read_excel( ‘文件名’,‘表名’ ) 即可加载该文件并存成 DataFrame 形式
|
|
Unnamed: 0 | 0 | 1 | 2 | |
---|---|---|---|---|
0 | 0 | 1 | 2 | 3 |
1 | 1 | 4 | 5 | 6 |
csv 格式
用 pd.to_csv 函数将 DataFrame 保存为 .csv 格式,注意如果 index 没有特意设定,最后不要把 index 值存到 csv 文件中。具体写法如下:
pd.to_csv( ‘文件名’,index=False )
|
|
用 pd.read_csv( ‘文件名’ ) 即可加载该文件并存成 DataFrame 形式
|
|
Code | Name | Market | Price | Currency | |
---|---|---|---|---|---|
0 | BABA | 阿里巴巴 | US | 185.35 | USD |
1 | 00700.HK | 腾讯 | HK | 380.20 | HKD |
2 | AAPL | 苹果 | US | 197.00 | USD |
3 | 600519.SH | 茅台 | SH | 900.20 | CNY |
如果一开始储存 df 的时候用 index=True,会发现加载完后的 df2 是以下的样子。
df2 里面第一栏是 df 的 index,由于没有具体的 columns 名称,系统给它一个 “Unamed: 0”。因此在存储 df 的时候,如果 df.index 没有特意设定,记住要在 to_csv() 中把 index 设置为 False。
3 数据表的索引和切片
由于索引/切片 Series 跟 numpy 数组很类似,因此本节只专注于对 DataFrame 做索引和切片。本节以下面 df 为例做展示。
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
BABA | 电商 | 176.92 | 16175610 | 101550 |
JD | 电商 | 25.95 | 27113291 | 175336 |
AAPL | 科技 | 172.97 | 18913154 | 100000 |
MS | 金融 | 41.79 | 10132145 | 60348 |
GS | 金融 | 196.00 | 2626634 | 36600 |
WMT | 零售 | 99.55 | 8086946 | 2200000 |
用不同颜色标注了 df 的 index, columns 和 values,可视图如下:
DataFrame 的索引或切片可以基于标签 (label-based) ,也可以基于位置 (position-based),不像 numpy 数组的索引或切片只基于位置。
DataFrame 的索引或切片有四大类:
-
索引单元素:
-
基于标签的 at
-
基于位置的 iat
-
-
切片 columns:
-
用 . 来切片单列
-
用 [] 来切片单列或多列
-
基于标签的 loc
-
基于位置的 iloc
-
-
切片 index:
-
用 [] 来切片单行或多行
-
基于标签的 loc
-
基于位置的 iloc
-
-
切片 index 和 columns:
-
基于标签的 loc
-
基于位置的 iloc
-
总体规律,基于标签就用 at 和 loc,基于位置就用 iat 和 iloc。下面来一类类分析:
3.1 索引单元素
两种方法来索引单元素,情况 1 基于标签 at,情况 2 基于位置 iat。
-
情况 1 - df.at[‘idx_i’, ‘attr_j’]
-
情况 2 - df.iat[i, j]
Python 里的中括号 [] 会代表很多意思,比如单元素索引,多元素切片,布尔索引等等,因此让 Python 猜你用的 [] 意图会很低效。如果想索引单元素,明明白白的用 at 和 iat 效率最高。
情况 1
|
|
172.97
用 at 获取「行标签」为 ‘AAPL’ 和「列标签」为 ‘价格’ 对应的元素。
情况 2
|
|
172.97
用 iat 获取第 3 行第 2 列对应的元素。
索引单元素的总结图:
3.2 切片 columns
切片单个 columns
切片单个 columns 会返回一个 Series,有以下四种情况。情况 1 用点 .;情况 2 用中括号 [];情况 3 基于标签 loc,情况 4 基于位置 iloc。
-
情况 1 - df.attr_i
-
情况 2 - df[‘attr_i’]
-
情况 3 - df.loc[:, ‘attr_i’]
-
情况 4 - df.iloc[:, i]
情况 1 记住就可以了,没什么可说的。
情况 2 非常像二维 numpy 数组 arr 的切片,用 arr[i] 就能获取 arr 在「轴 0」上的第 i 个元素 (一个 1darray),同理 df[‘attr_i’] 也能获取 df 的第 i 个 Series。
情况 3 和 4 的 loc 和 iloc 可类比于上面的 at 和 iat。带 i 的基于位置 (位置用整数表示,i 也泛指整数),不带 i 的基于标签。里面的冒号 : 代表所有的 index (和 numpy 数组里的冒号意思相同)。
个人建议,如果追求简洁和方便,用 . 和 [];如果追求一致和清晰,用 loc 和 iloc。
情况 1
|
|
代号
BABA 176.92
JD 25.95
AAPL 172.97
MS 41.79
GS 196.00
WMT 99.55
Name: 价格, dtype: float64
用 . 获取「价格」那一栏下的 Series。
情况 2
|
|
代号
BABA 176.92
JD 25.95
AAPL 172.97
MS 41.79
GS 196.00
WMT 99.55
Name: 价格, dtype: float64
用 [] 获取「价格」属性下的 Series。
情况 3
|
|
代号
BABA 16175610
JD 27113291
AAPL 18913154
MS 10132145
GS 2626634
WMT 8086946
Name: 交易量, dtype: int64
用 loc 获取「交易量」属性下的 Series。
情况 4
|
|
代号
BABA 电商
JD 电商
AAPL 科技
MS 金融
GS 金融
WMT 零售
Name: 行业, dtype: object
用 iloc 获取第 1 列下的 Series。
切片单个 columns 的总结图:
切片多个 columns
切片多个 columns 会返回一个 sub-DataFrame (原 DataFrame 的子集),有以下三种情况。情况 1 用中括号 [];情况 2 基于标签 loc,情况 3 基于位置 iloc。
-
情况 1 - df[[‘attr_i’, ‘attr_j’]]
-
情况 2 - df.loc[:, ‘attr_i’:‘attr_j’]
-
情况 3 - df.iloc[:, i:j]
和切片单个 columns 相比:
-
情况 1 用一个列表来储存一组属性 ‘attr_i’, ‘attr_j’,然后在放进中括号 [] 里获取它们
-
情况 2 用 ‘attr_i’:‘attr_j’ 来获取从属性 i 到属性 j 的 sub-DataFrame
-
情况 3 用 i:j 来获取从列 i+1 到列 j 的 sub-DataFrame
个人建议,如果追求简洁和方便,用 [];如果追求一致和清晰,用 loc 和 iloc。
情况 1
|
|
雇员 | 价格 | |
---|---|---|
代号 | ||
BABA | 101550 | 176.92 |
JD | 175336 | 25.95 |
AAPL | 100000 | 172.97 |
MS | 60348 | 41.79 |
GS | 36600 | 196.00 |
WMT | 2200000 | 99.55 |
用 [] 获取「雇员」和「价格」两个属性下的 sub-DataFrame。
情况 2
|
|
行业 | 价格 | 交易量 | |
---|---|---|---|
代号 | |||
BABA | 电商 | 176.92 | 16175610 |
JD | 电商 | 25.95 | 27113291 |
AAPL | 科技 | 172.97 | 18913154 |
MS | 金融 | 41.79 | 10132145 |
GS | 金融 | 196.00 | 2626634 |
WMT | 零售 | 99.55 | 8086946 |
用 loc 获取从属性 ‘行业’ 到 ‘交易量‘ 的 sub-DataFrame。
情况 3
|
|
行业 | 价格 | |
---|---|---|
代号 | ||
BABA | 电商 | 176.92 |
JD | 电商 | 25.95 |
AAPL | 科技 | 172.97 |
MS | 金融 | 41.79 |
GS | 金融 | 196.00 |
WMT | 零售 | 99.55 |
用 iloc 获取第 1 和 2 列下的 sub-DataFrame。
切片多个 columns 的总结图:
3.3 切片 index
切片单个 index
切片单个 index 有时会返回一个 Series,有以下两种情况。情况 1 基于标签 loc,情况 2 基于位置 iloc。
-
情况 1 - df.loc[‘idx_i’, :]
-
情况 2 - df.iloc[i, :]
切片单个 index 有时会返回一个只有一行的 DataFrame,有以下两种情况。情况 3 用中括号 [] 加「位置」,情况 4 用中括号 [] 加「标签」。
-
情况 3 - df[i:i+1]
-
情况 4 - df[‘idx_i’:‘idx_i’]
情况 1 和 2 的 loc 和 iloc 可类比于上面的 at 和 iat。带 i 的基于位置 (位置用整数表示,i 也泛指整数),不带 i 的基于标签。里面的冒号 : 代表所有的 columns (和 numpy 数组里的冒号意思相同)。
情况 3 用中括号 [] 加「位置」,位置 i:i+1 有前闭后开的性质。如果要获取第 i+1 行,需要用 i:i+1。
情况 4 用中括号 [] 加「标签」,标签没有前闭后开的性质。如果要获取标签 i,只需要用 ‘idx_i’:‘idx_i’。为什么不能只用 ‘idx_i’ 呢?原因是 Python 会把 df[‘idx_i’] 当成切片 columns,然后发现属性中没有 ‘idx_i’ 这一个字符,会报错的。
个人建议,只用 loc 和 iloc。情况 3 太麻烦,获取一行还要用 i:i+1。情况 4 的 df[‘idx_i’] 很容易和切片 columns 中的语句 df[‘attr_j’] 混淆。
情况 1
|
|
行业 金融
价格 196
交易量 2626634
雇员 36600
Name: GS, dtype: object
用 loc 获取标签为 ‘GS‘ 的 Series。(GS = Goldman Sachs = 高盛)
情况 2
|
|
行业 金融
价格 41.79
交易量 10132145
雇员 60348
Name: MS, dtype: object
用 iloc 获取第 4 行下的 Series。(MS = Morgan Stanley = 摩根斯坦利)
情况 3
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
JD | 电商 | 25.95 | 27113291 | 175336 |
用 [1:2] 获取第 2 行的 sub-DataFrame (只有一行)。
情况 4
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
JD | 电商 | 25.95 | 27113291 | 175336 |
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
JD | 电商 | 25.95 | 27113291 | 175336 |
AAPL | 科技 | 172.97 | 18913154 | 100000 |
MS | 金融 | 41.79 | 10132145 | 60348 |
用 [‘JD’:‘JD’] 获取标签为 ‘JD’ 的 sub-DataFrame (只有一行)。
切片单个 index 的总结图:
切片多个 index
切片多个 index 会返回一个 sub-DataFrame,有以下四种情况。情况 1 用中括号 [] 加「位置」,情况 2 用中括号 [] 加「标签」,情况 3 基于标签 loc,情况 4 基于位置 iloc。
-
情况 1 - df[i:j]
-
情况 2 - df[‘idx_i’:‘idx_j’]
-
情况 3 - df.loc[‘idx_i’:‘idx_j’, :]
-
情况 4 - df.iloc[i:j, :]
和切片单个 index 相比:
-
情况 1 用 [i:j] 来获取行 i+1 到行 j 的 sub-DataFrame
-
情况 2 用 [‘idx_i’:‘idx_j’] 来获取标签 i 到标签 j 的 sub-DataFrame
-
情况 3 用 loc 加 ‘idx_i’:‘idx_j’ 来获取从标签 i 到标签 j 的 sub-DataFrame
-
情况 4 用 iloc 加 i:j 来获取从行 i+1 到行 j 的 sub-DataFrame
个人建议,只用 loc 和 iloc。情况 1 和 2 的 df[] 很容易混淆中括号 [] 里的到底是切片 index 还是 columns。
情况 1
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
JD | 电商 | 25.95 | 27113291 | 175336 |
AAPL | 科技 | 172.97 | 18913154 | 100000 |
MS | 金融 | 41.79 | 10132145 | 60348 |
用 [1:4] 获取第 2 到 4 行的 sub-DataFrame。
情况 2
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
GS | 金融 | 196.00 | 2626634 | 36600 |
WMT | 零售 | 99.55 | 8086946 | 2200000 |
用 [‘GS’:‘WMT’] 获取标签从’GS’ 到 ‘WMT’ 的 sub-DataFrame。(WMT = Walmart = 沃尔玛)
情况 3
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
MS | 金融 | 41.79 | 10132145 | 60348 |
GS | 金融 | 196.00 | 2626634 | 36600 |
用 loc 获取标签从 ‘MS‘ 到 ‘GS’ 的 sub-DataFrame。注意 ‘MS’:’GS’ 要按着 index 里面元素的顺序,要不然会返回一个空的 DataFrame,比如:
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 |
情况 4
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
JD | 电商 | 25.95 | 27113291 | 175336 |
AAPL | 科技 | 172.97 | 18913154 | 100000 |
用 iloc 获取第 2 到 3 行的 sub-DataFrame。
切片多个 index 的总结图:
3.4 切片 index 和 columns
切片多个 index 和 columns 会返回一个 sub-DataFrame,有以下两种情况。情况 1 基于标签 loc,情况 2 基于位置 iloc。
-
情况 1 - df.loc[‘idx_i’:‘idx_j’, ‘attr_k’:‘attr_l’]
-
情况 2 - df.iloc[i:j, k:l]
清清楚楚,明明白白,用 loc 和 iloc。
情况 1
|
|
价格 | 交易量 | 雇员 | |
---|---|---|---|
代号 | |||
GS | 196.00 | 2626634 | 36600 |
WMT | 99.55 | 8086946 | 2200000 |
用 loc 获取行标签从 ‘GS‘ 到 ‘WMT’,列标签从’价格’到最后的 sub-DataFrame。
情况 2
|
|
价格 | 交易量 | |
---|---|---|
代号 | ||
BABA | 176.92 | 16175610 |
JD | 25.95 | 27113291 |
用 iloc 获取第 1 到 2 行,第 1 到 2 列的 sub-DataFrame。
切片 index 和 columns 的总结图:
3.5 高级索引
高级索引 (advanced indexing) 可以用布尔索引 (boolean indexing) 和调用函数 (callable function) 来实现,两种方法都返回一组“正确”的索引,而且可以和 loc , iloc , [] 一起套用,具体形式有以下常见几种:
-
df.loc[布尔索引, :]
-
df.iloc[布尔索引, :]
-
df[布尔索引]
-
df.loc[调用函数, :]
-
df.iloc[调用函数, :]
-
df[调用函数]
还有以下罕见几种:
-
df.loc[:, 布尔索引]
-
df.iloc[:, 布尔索引]
-
df.loc[:, 调用函数]
-
df.iloc[:, 调用函数]
读者可以想一想为什么第一组形式「常见」而第二组形式「罕见」呢?(Hint: 看看两组里冒号 : 在不同位置,再想想 DataFrame 每一行和每一列中数据的特点)
布尔索引
在〖数组计算之 NumPy (上)〗提过,布尔索引就是用一个由布尔类型值组成的数组来选择元素的方法。
当要过滤掉雇员小于 100,000 人的公司,可以用 loc 加上布尔索引。
|
|
代号
BABA True
JD True
AAPL True
MS False
GS False
WMT True
Name: 雇员, dtype: bool
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
BABA | 电商 | 176.92 | 16175610 | 101550 |
JD | 电商 | 25.95 | 27113291 | 175336 |
AAPL | 科技 | 172.97 | 18913154 | 100000 |
WMT | 零售 | 99.55 | 8086946 | 2200000 |
一种更简便的表达形式是用 df[],但是个人不喜欢 [],总觉得会引起「到底在切片 index 还是 columns」的歧义。
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
BABA | 电商 | 176.92 | 16175610 | 101550 |
JD | 电商 | 25.95 | 27113291 | 175336 |
AAPL | 科技 | 172.97 | 18913154 | 100000 |
WMT | 零售 | 99.55 | 8086946 | 2200000 |
现在来看一个「罕见」例子,假如想找到所有值为整数型的 columns
|
|
行业 False
价格 False
交易量 True
雇员 True
dtype: bool
交易量 | 雇员 | |
---|---|---|
代号 | ||
BABA | 16175610 | 101550 |
JD | 27113291 | 175336 |
AAPL | 18913154 | 100000 |
MS | 10132145 | 60348 |
GS | 2626634 | 36600 |
WMT | 8086946 | 2200000 |
调用函数
调用函数是只能有一个参数 (DataFrame, Series) 并返回一组索引的函数。因为调用函数定义在 loc , iloc , [] 里面,因此它就像在〖Python编程基础〗提过的匿名函数。
当要找出交易量大于平均交易量的所有公司,可以用 loc 加上匿名函数 (这里 x 代表 df)。
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
BABA | 电商 | 176.92 | 16175610 | 101550 |
JD | 电商 | 25.95 | 27113291 | 175336 |
AAPL | 科技 | 172.97 | 18913154 | 100000 |
在上面基础上再加一个条件 – 价格要在 100 之上 (这里 x 还是代表 df)
|
|
行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|
代号 | ||||
BABA | 电商 | 176.92 | 16175610 | 101550 |
AAPL | 科技 | 172.97 | 18913154 | 100000 |
最后来看看价格大于 100 的股票 (注意这里 x 代表 df.价格)
|
|
代号
BABA 176.92
AAPL 172.97
GS 196.00
Name: 价格, dtype: float64
3.6 多层索引
层次化索引(hierarchical indexing)也叫多层索引,是pandas的一项重要功能,它使你能在一个轴上拥有多个(两个以上)索引级别。多层索引可以将「低维数据」升维到「高维数据」。抽象点说,它使你能以低维度形式处理高维度数据。
多层索引 Series
首先定义一个 Series,注意它的 index 是一个二维列表,列表第一行 dates 作为第一层索引,第二行 codes 作为第二层索引。
|
|
2019-04-01 BABA 190
JD 32
GS 196
2019-04-02 BABA 192
GS 200
2019-04-03 BABA 189
JD 31
2019-04-04 JD 30
GS 199
dtype: int64
这个 Series 存储了四天里若干股票的价格,2019-04-01 储存了阿里巴巴、京东和高盛的股价,2019-04-04 只储存了京东和高盛的股价。试想,如果不用多层索引的 Series,则需要用一个 DataFrame 来存储在这样的数据,把 index 设置成 dates,把 colums 设置成 codes。
来看看 Series 的多层 index 是如何表示的
|
|
MultiIndex([('2019-04-01', 'BABA'),
('2019-04-01', 'JD'),
('2019-04-01', 'GS'),
('2019-04-02', 'BABA'),
('2019-04-02', 'GS'),
('2019-04-03', 'BABA'),
('2019-04-03', 'JD'),
('2019-04-04', 'JD'),
('2019-04-04', 'GS')],
)
输出是一个 MultiIndex 的对象,里面有 levels 和 labels 二类信息。
知识点
索引既然分多层,那么肯定分「内层」和「外层」把,levels 就是描述层的先后的。levels 是一个二维列表,每一行只存储着「唯一」的索引信息:
-
dates 是第一层索引,有 4 个「唯一」元素
-
codes 是第二层索引,有 3 个「唯一」元素
但是 data 里面有九行啊,4 个 dates 和 3 个 codes 怎么能描述这九行信息呢?这就需要 labels 了。labels 也是一个二维列表:
-
第一行储存 dates 每个元素在 data 里的位置索引
-
第二行储存 codes 每个元素在 data 里的位置索引
用 [] 加第一层索引可以获取第一层信息。
|
|
BABA 192
GS 200
dtype: int64
同理,用 loc 加第一层索引也可以切片获取第一层信息。
|
|
2019-04-02 BABA 192
GS 200
2019-04-03 BABA 189
JD 31
2019-04-04 JD 30
GS 199
dtype: int64
此外,切片还可以在不同层上进行,下面 loc 中的冒号 : 表示第一层所有元素,‘GS’ 表示第二层标签为 ‘GS’。
|
|
2019-04-01 196
2019-04-02 200
2019-04-04 199
dtype: int64
多层索引 DataFrame
Series 只有 index,上面刚介绍完多层 index,DataFrame 有 index 和 columns,它们可以设置成多层吗?下面代码用 MultiIndex 函数创建「多层 index 」midx 和「多层columns」mcol。
midx 和 mcol 都是对象,各种都有 levels, labels, names 等性质。
|
|
概括 | 公司数据 | 交易数据 | |||
---|---|---|---|---|---|
细分 | 行业 | 雇员 | 价格 | 交易量 | |
地区 | 代号 | ||||
中国 | BABA | 电商 | 101550 | 176.92 | 16175610 |
JD | 电商 | 175336 | 25.95 | 27113291 | |
美国 | GS | 金融 | 60348 | 41.79 | 10132145 |
MS | 金融 | 36600 | 196.00 | 2626634 |
这个 DataFrame 的 index 和 columns 都有两层,严格来说是个四维数据。下面看看如何进行「多层索引」的操作吧。
在第一层 columns 的 ‘公司数据’ 和第二层 columns 的 ‘行业’ 做索引,得到一个含两层 index 的 Series。
|
|
地区 代号
中国 BABA 电商
JD 电商
美国 GS 金融
MS 金融
Name: (公司数据, 行业), dtype: object
在第一层 index 的 ‘中国’ 做切片,得到一个含两层 columns 的 DataFrame。
|
|
概括 | 公司数据 | 交易数据 | ||
---|---|---|---|---|
细分 | 行业 | 雇员 | 价格 | 交易量 |
代号 | ||||
BABA | 电商 | 101550 | 176.92 | 16175610 |
JD | 电商 | 175336 | 25.95 | 27113291 |
调位 level
如果不喜欢 index level 的顺序,可用 swaplevel 将它们调位。
|
|
概括 | 公司数据 | 交易数据 | |||
---|---|---|---|---|---|
细分 | 行业 | 雇员 | 价格 | 交易量 | |
代号 | 地区 | ||||
BABA | 中国 | 电商 | 101550 | 176.92 | 16175610 |
JD | 中国 | 电商 | 175336 | 25.95 | 27113291 |
GS | 美国 | 金融 | 60348 | 41.79 | 10132145 |
MS | 美国 | 金融 | 36600 | 196.00 | 2626634 |
|
|
概括 | 公司数据 | 交易数据 | |||
---|---|---|---|---|---|
细分 | 行业 | 雇员 | 价格 | 交易量 | |
地区 | 代号 | ||||
中国 | BABA | 电商 | 101550 | 176.92 | 16175610 |
JD | 电商 | 175336 | 25.95 | 27113291 | |
美国 | GS | 金融 | 60348 | 41.79 | 10132145 |
MS | 金融 | 36600 | 196.00 | 2626634 |
如果不喜欢 columns level 的顺序,也可用 swaplevel 将它们调位。
|
|
细分 | 行业 | 雇员 | 价格 | 交易量 | |
---|---|---|---|---|---|
概括 | 公司数据 | 公司数据 | 交易数据 | 交易数据 | |
地区 | 代号 | ||||
中国 | BABA | 电商 | 101550 | 176.92 | 16175610 |
JD | 电商 | 175336 | 25.95 | 27113291 | |
美国 | GS | 金融 | 60348 | 41.79 | 10132145 |
MS | 金融 | 36600 | 196.00 | 2626634 |
重设 index
有时候,一个 DataFrame 的一个或者多个 columns 适合做 index,这时可用 set_index 将它们设置为 index,如果要将 index 还原成 columns,那么用 reset_index 。
看下面这个例子。
|
|
地区 | 代号 | 行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|---|---|
0 | 中国 | BABA | 电商 | 176.92 | 16175610 | 101550 |
1 | 中国 | JD | 电商 | 25.95 | 27113291 | 175336 |
2 | 美国 | MS | 金融 | 41.79 | 10132145 | 60348 |
3 | 美国 | GS | 金融 | 196.00 | 2626634 | 36600 |
将「地区」和「代号」设置为第一层 index 和第二层 index。
|
|
行业 | 价格 | 交易量 | 雇员 | ||
---|---|---|---|---|---|
地区 | 代号 | ||||
中国 | BABA | 电商 | 176.92 | 16175610 | 101550 |
JD | 电商 | 25.95 | 27113291 | 175336 | |
美国 | MS | 金融 | 41.79 | 10132145 | 60348 |
GS | 金融 | 196.00 | 2626634 | 36600 |
将所有 index 变成 columns。
|
|
地区 | 代号 | 行业 | 价格 | 交易量 | 雇员 | |
---|---|---|---|---|---|---|
0 | 中国 | BABA | 电商 | 176.92 | 16175610 | 101550 |
1 | 中国 | JD | 电商 | 25.95 | 27113291 | 175336 |
2 | 美国 | MS | 金融 | 41.79 | 10132145 | 60348 |
3 | 美国 | GS | 金融 | 196.00 | 2626634 | 36600 |
4 总结
Pandas 里面的数据结构是多维数据表,细化为一维的 Series,二维的 DataFrame。
pd 多维数据表 = np 多维数组 + 描述
其中
-
Series = 1darray + index
-
DataFrame = 2darray + index + columns
pd 多维数据表和 np 多维数组之间的类比关系如下图所示。
【创建数据表】创建 Series, DataFrame 用下面语句
-
pd.Series(x, index=idx)
-
pd.DataFrame(x, index=idx, columns=col)
DataFrame 由多个 Series 组成,而 Series 非常类似于一维的 DataFrame,因此学 Pandas 把注意力放在 DataFrame 上即可。
【索引和切片数据表】在索引或切片 DataFrame,有很多种方法。最好记的而不易出错的是用基于位置的 at 和 loc,和基于标签的 iat 和 iloc,具体来说,索引用 at 和 iat,切片用 loc 和 iloc。带 i 的基于位置,不带 i 的基于标签。
用 MultiIndex 可以创建多层索引的对象,获取 DataFrame df 的信息可用
-
df.loc[1st].loc[2nd]
-
df.loc[1st].iloc[2nd]
-
df.iloc[1st].loc[2nd]
-
df.iloc[1st].iloc[2nd]
要调换 level 可用
-
df.index.swaplevel(0,1)
-
df.columns.swaplevel(0,1)
要设置和重设 index 可用
-
df.set_index( columns )
-
df.reset_index
下篇讨论 Pandas 系列的后三节,分别是
-
「数据表的合并和连接」
-
「数据表的重塑和透视」
-
「数据表的分组和整合」
Stay Tuned!