全文共 20592 字,63 幅图,预计阅读时间 52 分钟。【注:本帖小节 2.2 用万矿里的 WindPy 来下载金融数据】0引言

本文是 Python 系列的第六篇

  • Python 入门篇 (上)

  • Python 入门篇 (下)

  • 数组计算之 NumPy (上)

  • 数组计算之 NumPy (下)

  • 科学计算之 SciPy

  • 数据结构之 Pandas (上)

  • 数据结构之 Pandas (下)

  • 基本可视化之 Matplotlib

  • 统计可视化之 Seaborn

  • 交互可视化之 Bokeh

  • 炫酷可视化之 PyEcharts

  • 机器学习之 Sklearn

  • 深度学习之 TensorFlow

  • 深度学习之 Keras

  • 深度学习之 PyTorch

  • 深度学习之 MXnet

Pandas 是 Python 为解决数据分析而创建的,详情看官网 (https://pandas.pydata.org/)。 在使用 pandas 之前,需要引进它,语法如下:

import pandas

这样你就可以用 pandas 里面所有的内置方法 (build-in methods) 了,比如创建一维的 Series 和二维的 DataFrame

pandas.Series()pandas.DataFrame()

但是每次写 pandas 字数有点多,通常我们给 pandas 起个别名 pd,用以下语法,这样所有出现 pandas 的地方都可以用 pd 替代。

import pandas as pd

Pandas 里面的数据结构是「多维数据表」,学习它可以类比这 NumPy 里的「多维数组」。1/2/3 维的「多维数据表」分别叫做 Series (系列), DataFrame (数据帧) 和 Panel (面板),和1/2/3 维的「多维数组」的类比关系如下。

由于「系列」、「数据帧」和「面板」这些直译过来的中文名词听起来有些奇怪,在本帖还是直接用 Series, DataFrame 和 Panel。

对比 NumPy (np) 和 Pandas (pd) 每个维度下的数据结构,不难看出

pd 多维数据表 = np 多维数组 + 描述

其中

  • Series = 1darray + index

  • DataFrame = 2darray + index + columns

  • Panel = 3darray + index + columns + item

每个维度上的「索引」使得「多维数据表」比「多维数组」涵盖更多的信息,如下图,左边的 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 的容器

  • Panel三维数据。Panel 是 DataFrame 的容器

知识点最常见的数据类型是二维的 DataFrame,其中

  • 每行代表一个示例 (instance)

  • 每列代表一个特征 (feature)

DataFrame 可理解成是 Series 的容器,每一列都是一个 Series,或者 Series 是只有一列的 DataFramePanel 可理解成是 DataFrame 的容器。

接下来我们用代码来创建 pandas 数据表,有两种方式:

  1. 按步就班的用 pd.Series(), pd.DataFrame() 和 pd.Panel()

  2. 一步登天的用万矿里面的 WindPy API 读取

2.1

按部就班法

一维 Series 

创建 Series 只需用下面一行代码

pd.Series( x, index=idx )

其中 x 可以是

  1. 列表 (list)

  2. numpy 数组 (ndarray)

  3. 字典 (dict)

回顾在〖Python 入门篇 (下)〗讲的函数里可以设定不同参数,那么

  • x 是位置参数

  • index 是默认参数,默认值为 idx = range(0, len(x))

用列表

s = pd.Series([27.2, 27.65, 27.70, 28])s
0    27.201    27.652    27.703    28.00dtype: float64

打印出来并不仅仅是列表里面的浮点数,每个浮点数前面还有一个索引,在本例中是 0, 1, 2, 3。

因此在创建 Series 时,如果不显性设定 index,那么 Python 给定一个默认从 0 到 N-1 的值,其中 N 是 x 的长度。

Series s 也是一个对象,用 dir(s) 可看出关于 Series 所有的属性和内置函数,其中最重要的是

  • 用 s.values 打印 s 中的元素

  • 用 s.index 打印 s 中的元素对应的索引

s.values
array([27.2 , 27.65, 27.7 , 28.  ])
s.index
RangeIndex(start=0, stop=4, step=1)

不难发现,以上创建的 Series 和 numpy 数组比多了「索引」,但这种 0,1,2,3 的索引是在没有什么描述意义。实际上我们定义的 s 是海底捞在 2019 年 4 月 1 日到 2019 年 4 月 4 日的股价,那么用日期来当索引是不是更好些?

dates = pd.date_range('20190401',periods=4)s2 = pd.Series( [27.2, 27.65, 27.70, 28], index=dates )s2
2019-04-01    27.202019-04-02    27.652019-04-03    27.702019-04-04    28.00Freq: D, dtype: float64

显然,s2 比 s 包含的信息更多,这是 s2 的索引是一组日期对象,数据类型是 datetime64,频率是 D (天)。

s2.index
DatetimeIndex(['2019-04-01', '2019-04-02', '2019-04-03', '2019-04-04'],dtype='datetime64[ns]', freq='D')

你甚至还可以给 s2 命名,就叫海底捞股价如何?

s2.name = '海底捞股价's2
2019-04-01    27.202019-04-02    27.652019-04-03    27.702019-04-04    28.00Freq: D, Name: 海底捞股价, dtype: float64

用 numpy 数组

除了用列表,我们还可以用 numpy 数组来生成 Series。在下例中,我们加入缺失值 np.nan,并分析一下 Series 中另外 5 个属性或内置函数的用法:

  • len: s 里的元素个数

  • shape: s 的形状 (用元组表示)

  • count: s 里不含 nan 的元素个数

  • unique: 返回 s 里不重复的元素

  • value_counts: 统计 s 里非 nan 元素的出现次数

对照上面函数的用法,下面的输出一看就懂了吧。

s = pd.Series( np.array([27.2, 27.65, 27.70, 28, 28, np.nan]) )print( 'The length is', len(s) )print( 'The shape is', s.shape )print( 'The count is', s.count() )
The length is 6The shape is (6,)The count is 5
s.unique()
array([27.2 , 27.65, 27.7 , 28.  ,   nan])
s.value_counts()
28.00    227.70    127.65    127.20    1dtype: int64

用字典

创建 Series 还可以用字典。字典的「键值对」的「键」自动变成了 Series 的索引 (index),而「值」自动变成了Series 的值 (values)。代码如下 (下列用 name 参数来对 s3 命名)

data_dict = { 'BABA': 187.07, 'PDD': 21.83, 'JD': 30.79, 'BIDU': 184.77 }s3 = pd.Series(data_dict, name='中概股')s3.index.name = '股票代号's3
股票代号BABA    187.07PDD      21.83JD       30.79BIDU    184.77Name: 中概股, dtype: float64

给 s3 起名中概股是因为阿里巴巴 (BABA)、拼多多 (PDD)、京东 (JD) 和百度 (BIDU) 都是中国公司但在美国上市的。此外还可以给 index 命名为 '股票代号'。

现在假设我们的股票代号为

stock = ['FB', 'BABA', 'PDD', 'JD']s4 = pd.Series( sdata, index=stock )s4
FB        NaNBABA    160.0PDD      28.0JD       25.0dtype: float64

代号里多加了脸书 (FB),而 sdata 字典中没有 FB 这个键,因此生成的 s4 在 FB 索引下对应的值为 NaN。再者,代号里没有百度 (BIDU),因此 s4 里面没有 BIDU 对应的值 (即便 sdata 里面有)。

当两个 Series 进行某种操作时,比如相加,Python 会自动对齐不同 Series 的 index,如下面代码所示:

s3 + s4
BABA    320.0BIDU      NaNFB        NaNJD       50.0PDD      56.0dtype: float64

Series 是 Pandas 里面最基本的数据结构,但是对应每个索引只有一个元素 (比如一个日期对应一个股价),因此 Series 处理不了每个索引对应多个元素 (比如一个日期对应一个开盘价、收盘价、交易量等等)。而 DataFrame 可以解决这个问题。

二维 DataFrame 

创建 DataFrame 只需用下面一行代码

pd.DataFrame( x, index=idx,

columns=col )

其中 x 可以是

  1. 二维列表 (list)

  2. 二维 numpy 数组 (ndarray)

  3. 字典 (dict),其值是一维列表、numpy 数组或 Series

  4. 另外一个 DataFrame

回顾在〖Python 入门篇 (下)〗讲的函数里可以设定不同参数,那么

  • x 是位置参数

  • index 是默认参数,默认值为 idx = range(0, x.shape[0])

  • columns 是默认参数,默认值为 col = range(0, x.shape[1])

用列表或 numpy 数组

# df1 = pd.DataFrame( [[1, 2, 3], [4, 5, 6]] )df1 = pd.DataFrame( np.array([[1, 2, 3], [4, 5, 6]]) )df1

在创建 DataFrame 时,如果不显性设定 index 和 columns 时,那么Python 给它们默认值,其中

  • index = 0 到 r-1,r 是 x 的行数

  • colmns = 0 到 c-1,c 是 x 的列数

用对象为列表的字典

symbol = ['BABA', 'JD', 'AAPL', 'MS', 'GS', 'WMT']data = {'行业': ['电商', '电商', '科技', '金融', '金融', '零售'],        '价格': [176.92, 25.95, 172.97, 41.79, 196.00, 99.55],        '交易量': [16175610, 27113291, 18913154, 10132145, 2626634, 8086946],        '雇员': [101550, 175336, 100000, 60348, 36600, 2200000]}df2 = pd.DataFrame( data, index=symbol )df2.name='美股'df2.index.name = '代号'df2

字典的「键值对」的「键」自动变成了 DataFrame 的栏 (columns),而「值」自动变成了 DataFrame 的值 (values),而其索引 (index) 需要另外定义。

分别来看 df2 的 values, columns 和 index。

df2.values
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)
df2.columns
Index(['行业', '价格', '交易量', '雇员'], dtype='object')
df2.index
Index(['BABA', 'JD', 'AAPL', 'MS', 'GS', 'WMT'],      dtype='object', name='代号')

A查看 DataFrame

我们可以从头或从尾部查看 DataFrame 的 n 行,分别用 df2.head() 和 df2.tail(n),如果没有设定 n,默认值为 5 行。

df2.head()

df2.tail(3)

B统计 DataFrame

我们用 df2.describe() 还可以看看 DataFrame 每栏的统计数据。

df2.describe()

函数 describe() 只对「数值型变量」有用 (没有对「字符型变量」行业栏做统计),统计量分别包括个数、均值、标准差、最小值,25-50-75 百分数值,最大值。一般做数据分析第一步会用这个表大概看看

  • 数据是否有缺失值 (每个栏下的 count 是否相等)?

  • 数据是否有异常值 (最小值 min 和最大值 max 是否太极端)?

C升维 DataFrame

我们用 MultiIndex.from_tuples() 还可以赋予 DataFrame 多层索引 (实际上增加了维度,多层索引的 DataFrame 实际上是三维数据)。

df2.index = pd.MultiIndex.from_tuples(             [('中国公司','BABA'), ('中国公司','JD'),              ('美国公司','AAPL'), ('美国公司','MS'),              ('美国公司','GS'), ('美国公司','WMT')] )df2

在 MultiIndex.from_tuples() 中传递一个「元组的列表」,每个元组,比如 ('中国公司', 'BABA'),第一个元素中国公司是第一层 index,第二个元素BABA是第二层 index。

DataFrame 是 Series 的容器,那什么是 DataFrame 的容器?Panel!

三维 Panel 

首先需要指出的是 Panel 在未来版本中会被废除,因此不想花时间看的同学可跳过。

创建 Panel 只需用下面一行代码

pd.Panel( x, item=itm,

major_axis=n1,

minor_axis=n2 )

其中 x 可以是

  1. 三维列表 (list)

  2. 三维 numpy 数组 (ndarray)

  3. 字典 (dict),其值是 DataFrame

回顾在〖Python 入门篇 (下)〗讲的函数里可以设定不同参数,那么

  • x 是位置参数

  • items 是默认参数 (axis 0),默认值为 itm = range(0, number of DataFrame)

  • major_axis 是默认参数 (axis 1),默认值和 DataFrame 的默认 index 一样

  • minor_axis 是默认参数 (axis 2),默认值和 DataFrame 的默认 columns 一样

用 numpy 数组

pn = pd.Panel(np.random.randn(2, 5, 4))pn
'pandas.core.panel.Panel'>Dimensions: 2 (items) x 5 (major_axis) x 4 (minor_axis)Items axis: 0 to 1Major_axis axis: 0 to 4Minor_axis axis: 0 to 3

Panel pn 含有 2 个 DataFrame,items 为 0, 1;每个 DataFrame 有 5 行 4 列,因此 major_axis 为 0,1,2,3,4,而 minor_axis 为 0,1,2,3。

用对象为 DataFrame 的字典

dates = pd.date_range('20190401',periods=4)data = {'开盘价': [27.2, 27.65, 27.70, 28],        '收盘价': [27.1, 27.55, 27.45, 28.1]}df1 = pd.DataFrame( data, index=dates )data = {'开盘价': [367, 369.8, 378.2, 380.6],        '收盘价': [369.5, 370.1, 380, 382.1]}df2 = pd.DataFrame( data, index=dates )p_data = {'海底捞' : df1, '腾讯' : df2}pn = pd.Panel(p_data)pn
'pandas.core.panel.Panel'>Dimensions: 2 (items) x 4 (major_axis) x 2 (minor_axis)Items axis: 海底捞 to 腾讯Major_axis axis: 2019-04-01 00:00:00 to 2019-04-04 00:00:00Minor_axis axis: 开盘价 to 收盘价

分析上面的 Panel pn

  • 有 2 个 DataFrame,items 为 '海底捞' 和 ‘腾讯’

  • 每个 DataFrame 有 4 行 2 列

    • major_axis 从 2019-04-01 到 2019-04-04

    • minor_axis 为 ‘开盘价’ 和 ‘收盘价’

让我们来查看两个 DataFrame 的内容

pn['海底捞']

pn['腾讯']

上面这种 Panel 类型的数据在量化投资中还蛮常见,比如我们需要 10 个股票1 年时期OHLC 价格 (Open, High, Low, Close),Panel 的 Items, Major_axis 和 Minor_axis 正好可以存储这样的三维数据。如果 Panel 要废掉,那用什么容器来储存三维数据呢?

用多层索引 (Multi-index) 的 DataFrame!

df = pd.concat([df1, df2])code = ['海底捞', '腾讯']midx = [ (c, d) for c in code for d in dates ] df.index =pd.MultiIndex.from_tuples( midx )df

首先用 concat() 函数 (下帖的内容) 将 df1 和 df2 连接起来;再用「列表解析法」生成 midx,它是一个元组的列表,c 是股票代码,d 是日期;最后放入 MultiIndex.from_tuples() 生成有多层索引的 DataFrame。

2.2

一步登天法

不喜欢量化的读者可跳过本节,不影响本帖的完整性。

上节都是手敲一些数据来创建「多维数据表」的,现实中做量化分析时,数据量都会很大,一般都是从量化平台中或者下载好的 csv 中直接读取。本节介绍如何从量化平台「万矿」中读取数据来创建「多维数据表」的。

首先在 https://www.windquant.com 注册一个账号,点击「研究」后在点开一个 Notebook 作为你的研究环境 (这是要夸奖一下万矿的 Notebook 体验真的不错,而且数据质量方面还有万德保证)。

接着必须加载 WindPy,然后执行 w.start() 启动 API 接口:

from WindPy import *w.start()

WindPy 里面有几个获取数据的核心函数,分别是

  • 日期序列函数 wsd

  • 多维数据函数 wss

  • 行情数据函数 wsq

  • 分钟序列数据函数 wsi

日期序列函数 wsd

该函数支持股票、债券、基金、期货、指数等多种证券的基本资料、股东信息、市场行情、证券分析、预测评级、财务数据等各种数据,可以支持取单品种单指标多品种单指标单品种多指标的时间序列数据 (注:不支持多品种多指标)。函数定义如下

w.wsd(security, fields, startdate, enddate, options)

  • security = 证券代号,可以是 str 或 list

  • fields = 指标,可以是 str 或 list

  • startdate = 起始日,可以是 str 或 datetime

  • enddate = 起始日,可以是 str 或 datetime

  • options = 一些特定设置

单品种单指标

获取平安银行在 2019-04-01 到 2019-04-04 的收盘价。

code = "000001.SZ"factors = ["close"]startDate = "2019-04-01"endDate = "2019-04-04"data = w.wsd(code, factors, startDate, endDate, usedf=True )data
(0,                          CLOSE 2019-04-01 00:00:00.005  13.18 2019-04-02 00:00:00.005  13.36 2019-04-03 00:00:00.005  13.44 2019-04-04 00:00:00.005  13.86)

知识点当 usedf=True 时返回元组

  • 元组第一个元素为 ErrorCode,其为 0 时表示数据获取正常

  • 元组第二个元素为获取的数据 DataFrame,其中 index 列为时间,columns 为参数 Fields 各指标

上面结果 errorcode = 0,要获取 DataFrame 只需访问 data[1]

data[1]

单品种多指标

获取平安银行在 2019-04-01 到 2019-04-04 的开盘价、最低价、最高价和收盘价。

code = "000001.SZ"factors = "open,low,high,close"startDate = "2019-04-01"endDate = "2019-04-04"data = w.wsd(code, factors, startDate, endDate, usedf=True )data[1]

多品种单指标

获取平安银行、万科、茅台在 2019-04-01 到 2019-04-04 的收盘价。

code = ["000001.SZ","000002.SZ","600519.SH"]factors = "close"startDate = "2019-04-01"endDate = "2019-04-04"data = w.wsd(code, factors, startDate, endDate, usedf=True )data[1]

多维数据函数 wss

该函数同样支持股票、债券、基金、期货、指数等多种证券的基本资料、股东信息、市场行情、证券分析、预测评级、财务数据等各种数据。但是 wss 支持取多品种多指标某个时间点的截面数据。函数定义如下

w.wss(security, fields, option)

  • security = 证券代号,可以是 str 或 list

  • fields = 指标,可以是 str 或 list

  • options = 一些特定设置

获取平安银行、万科、茅台在 2018-12-31 的收盘价、交易量、每股盈余和 profit/GR。

date = "2018-12-31"codes = ["000001.SZ","000002.SZ","600519.SH"]factors = "close, volume, eps_basic, profittogr"data = w.wss( codes, factors,               "rptDate="+date+";currencyType=", usedf=True)data[1]

如果要看财务数据,万矿是取每个季度最后一天作为报告期,如取 2018 年的四个定期报告数据,那报告期设置分别为:

  • 一季报:2018-03-31

  • 半年报:2018-06-30

  • 三季报:2018-09-30

  • 年报:   2018-12-31

本例 2018-12-31 是年报的数据。

行情数据函数 wsq

该函数支持股票、债券、基金、期货、指数等多种证券品种的实时行情数据。函数定义如下

w.wsq(security, fields, func=None)

  • security = 证券代号,可以是 str 或 list

  • fields = 指标,可以是 str 或 list

  • func = 回调函数

获取易方达深证 100ETF 里所有成分中的各种行情指标。

ETF = w.wset("allfundhelddetail", "rptdate=20181231;windcode=159901.OF")codes = ETF.Data[2]fields = "rt_last,rt_vol,rt_chg,rt_pct_chg,rt_vwap,rt_ask1,rt_bid1"data = w.wsq( codes, fields )            data = pd.DataFrame( data.Data,                      index=data.Fields,                      columns=data.Codes).Tdata.head(3).append(data.tail(3))

读者肯定好奇第一行代码怎么来的?这里 wset 是专门收集数据集信息的函数,万矿做的好的东西是又一套 GUI 帮你生成第一行代码,展示如下:

点击「API 函数」下面的「WSET 数据集」会带给你以下界面。再选择「ETF 申购成分信息」。

点击下一步得到

看到没有第一行代码就这样生成了,获取数据的门槛迅速降低了好多。

分钟序列数据函数 wsi

该函数获取选定证券品种的分钟线数据,包含基本行情和部分技术指标的分钟数据,分钟周期为 1-60 min,技术指标参数可以自定义设置。函数定义如下

w.wsi(security, fields, starttime = None, endtime = None, options = None)

  • security = 证券代号,可以是 str 或 list

  • fields = 指标,可以是 str 或 list

  • startdate = 起始日,可以是 str 或 datetime

  • enddate = 起始日,可以是 str 或 datetime

  • options = 一些特定设置

获取中金所 IF 股指期货当月连续合约 2019-04-01 09:30:00 开始至 2019-04-01 09:40:00 的 1 分钟数据。

codes ='IF00.CFE'fields ='open, high, low, close'IF = w.wsi( codes, fields, '2019-04-01 09:30:00', '2019-04-01 09:40:00', "", usedf=True )  IF[1]

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( '文件名','表名' )

df = pd.DataFrame(np.array([[1, 2, 3], [4, 5, 6]]))df.to_excel('pd_excel.xlsx', sheet_name='Sheet1')

用 pd.read_excel( '文件名','表名' ) 即可加载该文件并存成 DataFrame 形式

df1 = pd.read_excel('pd_excel.xlsx', sheet_name='Sheet1')df1

csv 格式

用 pd.to_csv 函数将 DataFrame 保存为 .csv 格式,注意如果 index 没有特意设定,最后不要把 index 值存到 csv 文件中。具体写法如下:

pd.to_csv( '文件名',index=False )

data = {'Code': ['BABA', '00700.HK', 'AAPL', '600519.SH'],        'Name': ['阿里巴巴', '腾讯', '苹果', '茅台'],        'Market': ['US', 'HK', 'US', 'SH'],        'Price': [185.35, 380.2, 197, 900.2],        'Currency': ['USD', 'HKD', 'USD', 'CNY']}df = pd.DataFrame(data)df.to_csv('pd_csv.csv', index=False)

用 pd.read_csv( '文件名' ) 即可加载该文件并存成 DataFrame 形式

df2 = pd.read_csv('pd_csv.csv')df2

如果一开始储存 df 的时候用 index=True,你会发现加载完后的 df2 是以下的样子。

df2 里面第一栏是 df 的 index,由于没有具体的 columns 名称,系统给它一个 "Unamed: 0"。因此在存储 df 的时候,如果 df.index 没有特意设定,记住要在 to_csv() 中把 index 设置为 False

3数据表的索引和切片

由于索引/切片 Series 跟 numpy 数组很类似,由于 Panel 在未来会被废掉,因此本节只专注于对 DataFrame 做索引和切片。本节以下面 df 为例做展示。

symbol = ['BABA', 'JD', 'AAPL', 'MS', 'GS', 'WMT']data = {'行业': ['电商', '电商', '科技', '金融', '金融', '零售'],        '价格': [176.92, 25.95, 172.97, 41.79, 196.00, 99.55],        '交易量': [16175610, 27113291, 18913154, 10132145, 2626634, 8086946],        '雇员': [101550, 175336, 100000, 60348, 36600, 2200000]}df = pd.DataFrame( data, index=symbol )df.name='美股'df.index.name = '代号'df

用不同颜色标注了 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']

  • 情况 2df.iat[i, j]

Python 里的中括号 [] 会代表很多意思,比如单元素索引,多元素切片,布尔索引等等,因此让 Python 猜你用的 [] 意图会很低效。如果你想索引单元素,明明白白的用 at 和 iat 效率最高。

情况 1

df.at['AAPL','价格']
172.97

用 at 获取「行标签」为 'AAPL' 和「列标签」为 ‘价格’ 对应的元素。

情况 2

df.iat[2,1]
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

df.价格
代号BABA    176.92JD       25.95AAPL    172.97MS       41.79GS      196.00WMT      99.55Name: 价格, dtype: float64

用 . 获取「价格」那一栏下的 Series。

情况 2

df['价格']
代号BABA    176.92JD       25.95AAPL    172.97MS       41.79GS      196.00WMT      99.55Name: 价格, dtype: float64

用 [] 获取「价格」属性下的 Series。

情况 3

df.loc[:, '交易量']
代号BABA    16175610JD      27113291AAPL    18913154MS      10132145GS       2626634WMT      8086946Name: 交易量, dtype: int64

用 loc 获取「交易量」属性下的 Series。

情况 4

df.iloc[:, 0]
代号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

df[ ['雇员', '价格'] ]

用 [] 获取「雇员」和「价格」两个属性下的 sub-DataFrame。

情况 2

df.loc[:, '行业':'交易量']

用 loc 获取从属性 ‘行业’ 到 ‘交易量‘ 的 sub-DataFrame。

情况 3

df.iloc[:, 0:2]

用 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

df.loc[ 'GS', : ]
行业          金融价格         196交易量    2626634雇员       36600Name: GS, dtype: object

用 loc 获取标签为 ‘GS‘ 的 Series。(GS = Goldman Sachs = 高盛)

情况 2

df.iloc[ 3, : ]
行业           金融价格        41.79交易量    10132145雇员        60348Name: MS, dtype: object

用 iloc 获取第 4 行下的 Series。(MS = Morgan Stanley = 摩根斯坦利)

情况 3

df[1:2]

用 [1:2] 获取第 2 行的 sub-DataFrame (只有一行)。

情况 4

df['JD':'JD']

用 ['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

df[ 1:4 ]

用 [1:4] 获取第 2 到 4 行的 sub-DataFrame。

情况 2

df[ 'GS':'WMT' ]

用 ['GS':'WMT'] 获取标签从'GS' 到 'WMT' 的 sub-DataFrame。(WMT = Walmart = 沃尔玛)

情况 3

df.loc[ 'MS':'GS', : ]

用 loc 获取标签从 ‘MS‘ 到 'GS' 的 sub-DataFrame。注意 ‘MS’:’GS’ 要按着 index 里面元素的顺序,要不然会返回一个空的 DataFrame,比如:

df.loc[ 'MS':'JD', : ]

情况 4

df.iloc[ 1:3, : ]

用 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

df.loc[ 'GS':'WMT', '价格': ]

用 loc 获取行标签从 ‘GS‘ 到 'WMT',列标签从'价格'到最后的 sub-DataFrame。

情况 2

df.iloc[ :2, 1:3 ]

用 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 加上布尔索引。

print( df.雇员 >= 100000 )df.loc[ df.雇员 >= 100000, : ]

一种更简便的表达形式是用 df[],但是我个人不喜欢 [],总觉得会引起「到底在切片 index 还是 columns」的歧义。

df[ df.雇员 >= 100000 ]

现在来看一个「罕见」例子,假如我们想找到所有值为整数型的 columns

print( df.dtypes == 'int64' )df.loc[ :, df.dtypes == 'int64' ]

调用函数

调用函数是只能有一个参数 (DataFrame, Series) 并返回一组索引的函数。因为调用函数定义在 loc , iloc , [] 里面,因此它就像在〖Python 入门篇 (下)〗提过的匿名函数。

当我们要找出交易量大于平均交易量的所有公司,我们可以用 loc 加上匿名函数 (这里 x 代表 df)。

df.loc[ lambda x: x.交易量 > x.交易量.mean() , : ]

在上面基础上再加一个条件 -- 价格要在 100 之上 (这里 x 还是代表 df)

df.loc[ lambda x: (x.交易量 > x.交易量.mean())                 & (x.价格 > 100), : ]

最后来看看价格大于 100 的股票 (注意这里 x 代表 df.价格)

df.价格.loc[ lambda x: x > 100 ]
代号BABA    176.92AAPL    172.97GS      196.00Name: 价格, dtype: float64

3.6

多层索引

在 Panel 那节已经提到过,多层索引可以将「低维数据」升维到「高维数据」,此外,多层索引还可以。。。。

多层索引 Series

首先定义一个 Series,注意它的 index 是一个二维列表,列表第一行 dates 作为第一层索引,第二行 codes 作为第二层索引。

price = [190,32,196,192,200,189,31,30,199]dates = ['2019-04-01']*3 + ['2019-04-02']*2       +['2019-04-03']*2 + ['2019-04-04']*2codes = ['BABA','JD','GS','BABA','GS','BABA','JD','JD','GS']data = pd.Series( price,                  index=[ dates, codes ])data
2019-04-01  BABA    190                       JD       32                       GS      1962019-04-02  BABA    192                       GS      2002019-04-03  BABA    189                       JD       312019-04-04  JD       30                       GS      199dtype: int64

这个 Series 存储了四天里若干股票的价格,2019-04-01 储存了阿里巴巴、京东和高盛的股价,2019-04-04 只储存了京东和高盛的股价。试想,如果不用多层索引的 Series,我们需要用一个 DataFrame 来存储在这样的数据,把 index 设置成 dates,把 colums 设置成 codes。

让我们看看 Series 的多层 index 是如何表示的

data.index
MultiIndex(levels=[['2019-04-01', '2019-04-02', '2019-04-03', '2019-04-04'],                   ['BABA', 'GS', 'JD']],                      labels=[[0, 0, 0, 1, 1, 2, 2, 3, 3],                   [0, 2, 1, 0, 1, 0, 2, 2, 1]])

输出是一个 MultiIndex 的对象,里面有 levels 和 labels 二类信息。

知识点

索引既然分多层,那么肯定分「内层」和「外层」把,levels 就是描述层的先后的。levels 是一个二维列表,每一行只存储着「唯一」的索引信息:

  • dates 是第一层索引,有 4 个「唯一」元素

  • codes 是第二层索引,有 3 个「唯一」元素

但是 data 里面有九行啊,4 个 dates 和 3 个 codes 怎么能描述这九行信息呢?这就需要 labels 了。labels 也是一个二维列表:

  • 第一行储存 dates 每个元素在 data 里的位置索引

  • 第二行储存 codes 每个元素在 data 里的位置索引

用 [] 加第一层索引可以获取第一层信息。

data['2019-04-02']
BABA    192GS   200dtype: int64

同理,用 loc 加第一层索引也可以切片获取第一层信息。

data.loc['2019-04-02':'2019-04-04']
2019-04-02  BABA    192                       GS      2002019-04-03  BABA    189                       JD       312019-04-04  JD       30                       GS      199dtype: int64

此外,切片还可以在不同层上进行,下面 loc  中的冒号 : 表示第一层所有元素,‘GS’ 表示第二层标签为 ‘GS’。

data.loc[ :, 'GS' ]
2019-04-01    1962019-04-02    2002019-04-04    199dtype: int64

多层索引 DataFrame

Series 只有 index,上面刚介绍完多层 index,DataFrame 有 index 和 columns,它们可以设置成多层吗?下面代码用 MultiIndex 函数创建「多层 index 」midx 和「多层columns」mcol。

midx 和 mcol 都是对象,各种都有 levels, labels, names 等性质。

data = [ ['电商', 101550, 176.92, 16175610],          ['电商', 175336, 25.95, 27113291],          ['金融', 60348, 41.79, 10132145],          ['金融', 36600, 196.00, 2626634] ]midx = pd.MultiIndex(           levels=[['中国','美国'],                  ['BABA', 'JD', 'GS', 'MS']],           labels=[[0,0,1,1],[0,1,2,3]],          names=['地区', '代号'])mcol = pd.MultiIndex(           levels=[['公司数据','交易数据'],                  ['行业','雇员','价格','交易量']],           labels=[[0,0,1,1],[0,1,2,3]],          names=['概括','细分'])df = pd.DataFrame(data, index=midx, columns=mcol)df

这个 DataFrame 的 index 和 columns 都有两层,严格来说是个四维数据。下面看看如何进行「多层索引」的操作吧。

在第一层 columns 的 ‘公司数据’ 和第二层 columns 的 ‘行业’ 做索引,得到一个含两层 index 的 Series。

# 1st level-1 column, 2nd level-2 columndf['公司数据','行业']
地区  代号  中国  BABA    电商         JD   电商美国  GS   金融         MS   金融Name: (公司数据, 行业), dtype: object

在第一层 index 的 ‘中国’ 做切片,得到一个含两层 columns 的 DataFrame。

df.loc['中国'].loc['BABA':'JD']

调位 level

如果你不喜欢 index level 的顺序,可用 swaplevel 将它们调位。

df.swaplevel('地区', '代号')

如果你不喜欢 columns level 的顺序,也可用 swaplevel 将它们调位。

df.columns = df.columns.swaplevel(0,1)df

重设 index

有时候,一个 DataFrame 的一个或者多个 columns 适合做 index,这时可用 set_index 将它们设置为 index,如果要将 index 还原成 columns,那么用 reset_index 。

看下面这个例子。

data = {'地区': ['中国', '中国', '美国', '美国'],        '代号': ['BABA', 'JD', 'MS', 'GS'],        '行业': ['电商', '电商', '金融', '金融'],        '价格': [176.92, 25.95, 41.79, 196.00],        '交易量': [16175610, 27113291, 10132145, 2626634],        '雇员': [101550, 175336, 60348, 36600] }df = pd.DataFrame( data )df

将「地区」和「代号」设置为第一层 index 和第二层 index。

df2 = df.set_index( ['地区','代号'] )df2

将所有 index 变成 columns。

df2.reset_index()

4总结

Pandas 里面的数据结构是多维数据表,细化为一维的 Series,二维的 DataFrame,三维的 Panel。

多维数据表 = 多维数组 + 描述

其中

  • Series = 1darray + index

  • DataFrame = 2darray + index + columns

  • Panel = 3darray + index + columns + item

pd 多维数据表和 np 多维数组之间的类比关系如下图所示。

【创建数据表】创建 Series, DataFrame, Panel 用下面语句

  • pd.Series(x, index=idx)

  • pd.DataFrame(x, index=idx, columns=col)

  • pd.Panel(x, item=itm, major_axis=n1, minor_axis=n2)

DataFrame 由多个 Series 组成,Panel 有多个 DataFrame 组成。Series 非常类似于一维的 DataFrame,Panel 未来会被废掉,因此学 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!

pandas dataframe创建_Python数据分析基础之Pandas学习 (上)相关推荐

  1. python处理多个excel数据_python 数据分析基础 day8-pandas读写多个excel文件

    今天是读<python数据分析基础>的第8天,今天的读书笔记的内容为利用pandas读写多个excel文件,当中涉及到读写excel文件的多个工作表. 大致原理如下: glob.glob( ...

  2. azw3 python数据分析 活用pandas库_Python数据分析 活用Pandas库 数据科学教程数据分析入门图书 Python编程入门数据处理...

    内容介绍 本书是Python数据分析入门书,每个概念都通过简单实例来阐述,便于读者理解与上手.具体内容包括:Python及Pandas基础知识,加载和查看数据集,Pandas的DataFrame对象和 ...

  3. python数据分析 活用pandas库_Python数据分析:活用Pandas库:Pandas for everyone

    序 iv 前言 v 致谢 xi 关于作者 xiv 第 一部分 简介 1 第 1章 Pandas DataFrame基础知识 2 1.1 简介 2 1.2 加载数据集 3 1.3 查看列.行.单元格 5 ...

  4. python部分引入total值的问题_Python数据分析基础与过程综述,关键数据预处理异常点的发现与处理,python,及,流程,回顾,重点,之,值...

    一. python数据分析基础库的导入 基本是固定搭配 import numpy as np #科学计算基础库,多维数组对象ndarray import pandas as pd #数据处理库,Dat ...

  5. 关联分析python牛奶面包_Python数据分析基础ReadingDay13_关联分析Apriori

    reading Foundations for Analysis with Python Day 13 <Python数据分析基础>封面 之前我们已经完成了<Python数据分析基础 ...

  6. python给成绩表加上姓名列_Python数据分析基础练习

    Python数据分析基础(余本)第四章练习 需求 班主任现有一班级的两张表,如下: 信息表:成绩表:现请帮班主任做如下工作: 1) 给成绩表加上姓名列: 2) 给成绩表加上字段"总分&quo ...

  7. notebook python 内嵌 数据库_python数据分析:在jupyter notebook上使用pythonSQL做数据分析...

    python数据分析:在jupyter notebook上使用python&SQL做数据分析 发布时间:2019-01-14 21:14, 浏览次数:1143 , 标签: python jup ...

  8. python 统计分析 包_Python 数据分析包:pandas 基础

    pandas 是基于 Numpy 构建的含有更高级数据结构和工具的数据分析包 类似于 Numpy 的核心是 ndarray,pandas 也是围绕着 Series 和 DataFrame 两个核心数据 ...

  9. python嵌套字典的建立_python – 从Pandas DataFrame创建复杂的嵌套字典

    不是很简洁,但它是我现在能得到的最好的: >>> def rollup1(x): ... return x.set_index('test')[['grade', 'pass']]. ...

最新文章

  1. 搭建nexus后,进入首页的时候出现warning: Could not connect to Nexus.错误
  2. 一个理性战胜感性的成功案例:P2P投资和活期理财,纠结中提炼出来的1个数学问题...
  3. odoo10学习笔记十四:mixin其他功能模块
  4. Class_fileAndroid应用调用方法
  5. placement new操作符
  6. 保姆级计算机视觉学习路线
  7. mysql 模糊查询之特殊字符下划线 _
  8. 【C/C++】转义字符大全
  9. Unity自学虚拟摇杆
  10. docker20.10.7及以下版本安装registry镜像库指引
  11. SmartBI常用报表宏代码
  12. cmd中文输入和显示问题
  13. 广告召回率是什么意思_准确率、精确率、召回率的含义
  14. Ubuntu 18.04 16.04 设置输入法切换方法 中文输入法
  15. 优质的教育直播平台源码是如何进行在线教育系统搭建的
  16. VOLO: Vision Outlooker for Visual Recognition 阅读笔记
  17. 毕业设计-基于SSM校园线上购物系统
  18. Niushop 物流公司设置
  19. android如何在app端设置USB调试模式
  20. MySQL中间件Atlas安装及使用

热门文章

  1. html中2d变形兼容性,CSS3(3)---2D变形(transform)
  2. linux虚拟环境tmux,关于在Linux系统中如何把tmux和vim打造成IDE的讲解
  3. 剑指offer面试题30. 包含min函数的栈(辅助栈)
  4. Python基础——input交互功能
  5. 机器学习——卷积神经网络计算
  6. Session实现验证码登陆笔记
  7. 【vue】 生命周期
  8. SQL SERVER中的二种获得自增长ID的方法
  9. Python 列表下标操作
  10. 分页 Paginator