一、Pandas介绍
在Python中, pandas 包含了高级的数据结构 Series 和 DataFrame ,使得在Python中处理数据变得非常方便、快速和简单。
pandas 不同的版本之间存在一些不兼容性,为此,我们需要清楚使用的是哪一个版本的 pandas 。
import pandas as pd
print(pd.__version__)
1.3.1
pandas 主要的两个数据结构是 Series 和 DataFrame ,我们先导入它们以及相关模块:
import numpy as np
import pandas as pd
from pandas import Series, DataFrame
二、Pandas数据结构:Series
Series 也可以叫做 序列。从一般意义上来讲, Series 可以简单地被认为是一维的数组。 Series 和一维数组最主要的区别在于 Series 类型具有索引( index ),可以和另一个编程中常见的数据结构哈希(Hash)联系起来。其次,Series 的元素通常类型统一,而一维数组可以是不同类型的元素组成。
2.1 创建 Series
创建一个 Series 的基本格式是 s = Series(data, index=index, name=name)
,以下给出几个创建 Series 的例子 :
a = np.random.randn(5)
print("a is an array:")
print(a)
s = Series(a)
print("s is a Series:")
print(s)
a is an array:
[-0.3007983 0.2893125 -0.10106809 -1.06076531 0.29202818]
s is a Series:
0 -0.300798
1 0.289313
2 -0.101068
3 -1.060765
4 0.292028
dtype: float64
可以在创建 Series 时添加 index ,并可使用 Series.index 查看具体 的 index 。需要注意的一点是,当从数组创建 Series 时,若指定 index ,那 么 index 长度要和 data 的长度一致:
s = Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'])
print(s)
print(s.index)
a 1.192283
b 1.477963
c -0.386441
d 1.622310
e 0.845787
dtype: float64
Index(['a', 'b', 'c', 'd', 'e'], dtype='object')
创建 Series 的另一个可选项是 name ,可指定 Series 的名称,可 用 Series.name 访问。在随后的 DataFrame 中,每一列的列名在该列被单独取 出来时就成了 Series 的名称:
s = Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'],
name='my_series')
print(s)
print(s.name)
a -2.240155
b 0.258177
c 0.343206
d 1.220887
e -0.153971
Name: my_series, dtype: float64
my_series
Series 还可以从字典( dict )创建:
d = {'a': 0., 'b': 1, 'c': 2}
print("d is a dict:")
print(d)
s = Series(d)
print("s is a Series:")
print(s)
d is a dict:
{'a': 0.0, 'b': 1, 'c': 2}
s is a Series:
a 0.0
b 1.0
c 2.0
dtype: float64
让我们来看看使用字典创建 Series 时指定 index 的情形( index 长度不必和 字典相同):
s = Series(d, index=['b', 'c', 'd', 'a'])
print(s)
b 1.0
c 2.0
d NaN
a 0.0
dtype: float64
我们可以观察到两点:一是字典创建的 Series ,数据将按 index 的顺序重新排 列;二是 index 长度可以和字典长度不一致,如果多了的话, pandas 将自动为 多余的 index 分配 NaN (not a number, pandas 中数据缺失的标准记号),当 然 index 少的话就截取部分的字典内容。
如果数据就是一个单一的变量,如数字4,那么 Series 将重复这个变量:
s = Series(4., index=['a', 'b', 'c', 'd', 'e'])
print(s)
a 4.0
b 4.0
c 4.0
d 4.0
e 4.0
dtype: float64
2.2 Series 数据的访问
访问 Series 数据可以和数组一样使用下标,也可以像字典一样使用索引,还可以使用一些条件过滤:
s = Series(np.random.randn(10),index=['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'])print(s[0])0.20339387093082803
print(s[[0]])a 0.906648dtype: float64
注:s[0] 和 s[[0]] 的区别在于,s[0] 仅取值,而 s[[0]] 是取行,会保留原来的序列类型
print(s[:2])a -2.028119b 0.061965dtype: float64
print(s[[2,0,4]])c -0.526092a 0.484422e 1.571355dtype: float64
print(s[['e', 'i']])e 0.435927i 1.045612dtype: float64
print(s[s > 0.5])c 0.995218e 0.858984h 0.942102i 0.675896dtype: float64
print('e' in s)True
三、Pandas数据结构: DataFrame
DataFrame也叫数据结构。在使用 DataFrame 之前,我们说明一下 DataFrame 的特性。 DataFrame 是将数个 Series 按列合并而成的二维数据结构,每一列单独取出来是一个 Series ,这和SQL数据库中取出的数据是很类似的。所以,按列对一个 DataFrame 进行处理更为方便,用户在编程时注意培养按列构建数据的思维。 DataFrame 的优势在于可以方便地处理不同类型的列,因此,就不要考虑如何对一个全是浮点数的 DataFrame 求逆之类的问题了,处理这种问题还是把数据 存成 NumPy 的 matrix 类型比较便利一些。
3.1 创建 DataFrame
首先来看如何从字典创建 DataFrame 。 DataFrame 是一个二维的数据结构,是 多个 Series 的集合体。我们先创建一个值是 Series 的字典,并转换为 DataFrame :
d = {'one': Series([1., 2., 3.], index=['a', 'b', 'c']), 'two':Series([1., 2., 3., 4.], index=['a', 'b', 'c', 'd'])}df = DataFrame(d)print(df) one twoa 1.0 1.0b 2.0 2.0c 3.0 3.0d NaN 4.0
可以指定所需的行和列,若字典中不含有对应的元素,则置为 NaN :
df = DataFrame(d, index=['r', 'd', 'a'], columns=['two', 'three'])print(df) two threer NaN NaNd 4.0 NaNa 1.0 NaN
可以使用 dataframe.index 和 dataframe.columns 来查看 DataFrame 的行和列, dataframe.values 则以数组的形式返回 DataFrame 的元素:
print("DataFrame index:")print(df.index)print("DataFrame columns:")print(df.columns)print("DataFrame values:")print(df.values)DataFrame index:Index(['a', 'b', 'c', 'd'], dtype='object')DataFrame columns:Index(['one', 'two'], dtype='object')DataFrame values:[[ 1. 1.] [ 2. 2.] [ 3. 3.] [nan 4.]]
DataFrame 也可以从值是数组的字典创建,但是各个数组的长度需要相同:
d = {'one': [1., 2., 3., 4.], 'two': [4., 3., 2., 1.]}df = DataFrame(d, index=['a', 'b', 'c', 'd'])print(df) one twoa 1.0 4.0b 2.0 3.0c 3.0 2.0d 4.0 1.0
值非数组时,没有这一限制,并且缺失值补成 NaN :
d= [{'a': 1.6, 'b': 2}, {'a': 3, 'b': 6, 'c': 9}]df = DataFrame(d)print(df) a b c0 1.6 2 NaN1 3.0 6 9.0
在实际处理数据时,有时需要创建一个空的 DataFrame ,可以这么做:
df = DataFrame()print(df)Empty DataFrameColumns: []Index: []
另一种创建 DataFrame 的方法十分有用,那就是使用 concat 函数基于 Series 或者 DataFrame 创建一个 DataFrame
a = Series(range(5))b = Series(np.linspace(4, 20, 5))df = pd.concat([a, b], axis=1)print(df) 0 10 0 4.01 1 8.02 2 12.03 3 16.04 4 20.0
其中的 axis=1 表示按行进行合并, axis=0 表示按列合并,并且, Series 都处理成一列,所以这里如果选 axis=0 的话,将得到一个 10×1 的 DataFrame 。 下面这个例子展示了如何按行合并 DataFrame 成一个大的 DataFrame :
df = DataFrame()index = ['alpha', 'beta', 'gamma', 'delta', 'eta']for i in range(5): a = DataFrame([np.linspace(i, 5*i, 5)], index=[index[i]]) df = pd.concat([df, a], axis=0)print(df) 0 1 2 3 4alpha 0.0 0.0 0.0 0.0 0.0beta 1.0 2.0 3.0 4.0 5.0gamma 2.0 4.0 6.0 8.0 10.0delta 3.0 6.0 9.0 12.0 15.0eta 4.0 8.0 12.0 16.0 20.0
3.2 DataFrame 数据的访问
首先,再次强调一下 DataFrame 是以列作为操作的基础的,全部操作都想象成先 从 DataFrame 里取一列,再从这个 Series 取元素即可。可以 用 datafrae.column_name 选取列,也可以使用 dataframe[] 操作选取列,我 们可以马上发现前一种方法只能选取一列,而后一种方法可以选择多列。 若 DataFrame 没有列名, [] 可以使用非负整数,也就是“下标”选取列;若有列 名,则必须使用列名选取,另外 datafrae.column_name 在没有列名的时候是无效的:
print(df[1])print(type(df[1]))alpha 0.0beta 2.0gamma 4.0delta 6.0eta 8.0Name: 1, dtype: float64<class 'pandas.core.series.Series'>
print(df[[1]])print(type(df[[1]])) 1alpha 0.0beta 2.0gamma 4.0delta 6.0eta 8.0<class 'pandas.core.frame.DataFrame'>
注:与2.2节中的 Series 类似,df[1] 获取某一列,类型变为 Series;df[[1]] 获取某一列,保持 DataFrame 结构
df.columns = ['a', 'b', 'c', 'd', 'e']print(df['b'])print(type(df['b']))alpha 0.0beta 2.0gamma 4.0delta 6.0eta 8.0Name: b, dtype: float64<class 'pandas.core.series.Series'>
print(df[['a', 'd']])print(type(df[['a', 'd']])) a dalpha 0.0 0.0beta 1.0 4.0gamma 2.0 8.0delta 3.0 12.0eta 4.0 16.0<class 'pandas.core.frame.DataFrame'>
以上代码使用了 dataframe.columns 为 DataFrame 赋列名,并且我们看到单独 取一列出来,其数据结构显示的是 Series ,取两列及两列以上的结果仍然是 DataFrame 。访问特定的元素可以如 Series 一样使用下标或者是索引:
print(df['b'][2])print(df['b']['gamma'])4.04.0
若需要选取行,可以使用 dataframe.iloc 按下标选取,或者使 用 dataframe.loc 按标签选取:
print(df.iloc[1])print(df.loc['beta'])a 1.0b 2.0c 3.0d 4.0e 5.0Name: beta, dtype: float64a 1.0b 2.0c 3.0d 4.0e 5.0Name: beta, dtype: float64
注:loc 基于标签,iloc 基于下标。如果 iloc 使用标签会报错,反之 loc 使用下标会报错。
例:正确用法为,df.iloc[0][0] 或 df.loc[‘a’][‘beta’]
选取行还可以使用切片的方式或者是布尔类型的向量:
print("Selecting by slices:")print(df[1:3])bool_vec = [True, False, True, True, False]print("Selecting by boolean vector:")print(df[bool_vec])Selecting by slices: a b c d ebeta 1.0 2.0 3.0 4.0 5.0gamma 2.0 4.0 6.0 8.0 10.0Selecting by boolean vector: a b c d ealpha 0.0 0.0 0.0 0.0 0.0gamma 2.0 4.0 6.0 8.0 10.0delta 3.0 6.0 9.0 12.0 15.0
行列组合起来选取数据:
# 先列后行print(df[['b', 'd']].iloc[[1, 3]])# 先行后列print(df.iloc[[1, 3]][['b', 'd']])# 同上df.iloc[1, 3]['b', 'd']# 先列后行print(df[['b', 'd']].loc[['beta', 'delta']])# 先行后列print(df.loc[['beta', 'delta']][['b', 'd']])# 同上df.loc['beta', 'delta']['b', 'd'] b dbeta 2.0 4.0delta 6.0 12.0 b dbeta 2.0 4.0delta 6.0 12.0 b dbeta 2.0 4.0delta 6.0 12.0 b dbeta 2.0 4.0delta 6.0 12.0
如果不是需要访问特定行列,而只是某个特殊位置的元素的 话, dataframe.at 和 dataframe.iat 是最快的方式,它们分别用于使用索引 和下标进行访问:
print(df.iat[2, 3])print(df.at['gamma', 'd'])8.08.0
四、进阶:Pandas 数据操作
掌握本章操作之后,基本可以处理大多数的数据了。为了看数据方便一些,我们设置一下输出屏幕的宽度
pd.set_option('display.width', 200)
4.1 数据创建的其它方式
数据结构的创建不止是之前介绍的标准形式,例如,我们可以创建一个以日期为元素的 Series :
dates = pd.date_range('20150101', periods=5)print(dates)DatetimeIndex(['2015-01-01', '2015-01-02', '2015-01-03', '2015-01-04', '2015-01-05'], dtype='datetime64[ns]', freq='D')
将这个日期 Series 作为索引赋给一个 DataFrame :
df = pd.DataFrame(np.random.randn(5, 4),index=dates,columns=list('ABCD'))print(df) A B C D2015-01-01 0.008608 -0.686443 -0.021788 0.4344862015-01-02 0.711034 0.746027 1.528270 0.5572102015-01-03 -0.334801 0.532736 1.006003 0.0303722015-01-04 0.507740 0.668962 -0.166262 0.5183842015-01-05 0.887693 -0.839035 0.998530 1.066598
只要是能转换成 Series 的对象,都可以用于创建 DataFrame :
df2 = pd.DataFrame({ 'A' : 1., 'B': pd.Timestamp('20150214'), 'C': pd.Series(1.6,index=list(range(4)),dtype='float64'), 'D' : np.array([4] * 4, dtype='int64'), 'E' : 'hello pandas!' })print(df2) A B C D E0 1.0 2015-02-14 1.6 4 hello pandas!1 1.0 2015-02-14 1.6 4 hello pandas!2 1.0 2015-02-14 1.6 4 hello pandas!3 1.0 2015-02-14 1.6 4 hello pandas!
4.2 数据的查看和排序
通过以下数据创建 DataFrame :
raw_data = [['000001.XSHE', '2015-01-05', '平安银行', 15.99, 16.28, 15.60, 16.02, 286043643], ['601998.XSHG', '2015-01-28', '中信银行', 7.04, 7.32, 6.95, 7.15, 163146128], ['000001.XSHE', '2015-01-07', '平安银行', 15.56, 15.83, 15.30, 15.48, 170012067], ['000001.XSHE', '2015-01-08', '平安银行', 15.50, 15.57, 14.90, 14.96, 140771421], ['000001.XSHE', '2015-01-09', '平安银行', 14.90, 15.87, 14.71, 15.08, 250850023], ['601998.XSHG', '2015-01-29', '中信银行', 6.97, 7.05, 6.90, 7.01, 93003445], ['000001.XSHE', '2015-01-06', '平安银行', 15.85, 16.39, 15.55, 15.78, 216642140], ['601998.XSHG', '2015-01-30', '中信银行', 7.10, 7.14, 6.92, 6.95, 68146718]]columns = ['secID', 'tradeDate', 'secShortName', 'openPrice', 'highestPrice', 'lowestPrice', 'closePrice', 'turnoverVol']df = DataFrame(raw_data, columns=columns)print(df) secID tradeDate secShortName openPrice highestPrice lowestPrice closePrice turnoverVol0 000001.XSHE 2015-01-05 平安银行 15.99 16.28 15.60 16.02 2860436431 601998.XSHG 2015-01-28 中信银行 7.04 7.32 6.95 7.15 1631461282 000001.XSHE 2015-01-07 平安银行 15.56 15.83 15.30 15.48 1700120673 000001.XSHE 2015-01-08 平安银行 15.50 15.57 14.90 14.96 1407714214 000001.XSHE 2015-01-09 平安银行 14.90 15.87 14.71 15.08 2508500235 601998.XSHG 2015-01-29 中信银行 6.97 7.05 6.90 7.01 930034456 000001.XSHE 2015-01-06 平安银行 15.85 16.39 15.55 15.78 2166421407 601998.XSHG 2015-01-30 中信银行 7.10 7.14 6.92 6.95 68146718[8 rows x 8 columns]
以上代码是2015年一月份两支股票的日行情信息,首先我们来 看一下数据的大小:
print(df.shape)(8, 8)
我们可以看到有8行,表示我们获取到了8条记录,每条记录有8个字段,现在预览一下数据, dataframe.head() 和 dataframe.tail() 可以查看数据的头五行和尾五行,若需要改变行数,可在括号内指定:
print("Head of this DataFrame:")print(df.head())print("Tail of this DataFrame:")print(df.tail(3))Head of this DataFrame: secID tradeDate secShortName ... lowestPrice closePrice turnoverVol0 000001.XSHE 2015-01-05 平安银行 ... 15.60 16.02 2860436431 601998.XSHG 2015-01-28 中信银行 ... 6.95 7.15 1631461282 000001.XSHE 2015-01-07 平安银行 ... 15.30 15.48 1700120673 000001.XSHE 2015-01-08 平安银行 ... 14.90 14.96 1407714214 000001.XSHE 2015-01-09 平安银行 ... 14.71 15.08 250850023[5 rows x 8 columns]Tail of this DataFrame: secID tradeDate secShortName ... lowestPrice closePrice turnoverVol5 601998.XSHG 2015-01-29 中信银行 ... 6.90 7.01 930034456 000001.XSHE 2015-01-06 平安银行 ... 15.55 15.78 2166421407 601998.XSHG 2015-01-30 中信银行 ... 6.92 6.95 68146718[3 rows x 8 columns]
dataframe.describe() 提供了 DataFrame 中纯数值数据的统计信息:
print(df.describe()) openPrice highestPrice lowestPrice closePrice turnoverVolcount 8.000000 8.000000 8.000000 8.00000 8.000000e+00mean 12.363750 12.681250 12.103750 12.30375 1.735769e+08std 4.422833 4.571541 4.300156 4.37516 7.490931e+07min 6.970000 7.050000 6.900000 6.95000 6.814672e+0725% 7.085000 7.275000 6.942500 7.11500 1.288294e+0850% 15.200000 15.700000 14.805000 15.02000 1.665791e+0875% 15.632500 15.972500 15.362500 15.55500 2.251941e+08max 15.990000 16.390000 15.600000 16.02000 2.860436e+08
对数据的排序将便利我们观察数据, DataFrame 提供了两种形式的排序。一种是按行列排序,即按照索引(行名)或者列名进行排序,可调用 dataframe.sort_index ,指定 axis=0 表示按索引(行名)排序, axis=1 表示按列名排序,并可指定升序(ascending=True)或者降序(ascending=False):
print("Order by column names, descending:")print(df.sort_index(axis=0, ascending=False).head())Order by column names, descending: secID tradeDate secShortName ... lowestPrice closePrice turnoverVol7 601998.XSHG 2015-01-30 中信银行 ... 6.92 6.95 681467186 000001.XSHE 2015-01-06 平安银行 ... 15.55 15.78 2166421405 601998.XSHG 2015-01-29 中信银行 ... 6.90 7.01 930034454 000001.XSHE 2015-01-09 平安银行 ... 14.71 15.08 2508500233 000001.XSHE 2015-01-08 平安银行 ... 14.90 14.96 140771421
第二种排序是按值排序,可指定列名和排序方式,默认的是升序排序:
print("Order by column value, ascending:")print(df.sort_values(by=['tradeDate']))print("Order by multiple columns value:")df = df.sort_values(by=['tradeDate', 'secID'], ascending=[False, True])print(df.head())Order by column value, ascending: secID tradeDate secShortName ... lowestPrice closePrice turnoverVol0 000001.XSHE 2015-01-05 平安银行 ... 15.60 16.02 2860436436 000001.XSHE 2015-01-06 平安银行 ... 15.55 15.78 2166421402 000001.XSHE 2015-01-07 平安银行 ... 15.30 15.48 1700120673 000001.XSHE 2015-01-08 平安银行 ... 14.90 14.96 1407714214 000001.XSHE 2015-01-09 平安银行 ... 14.71 15.08 2508500231 601998.XSHG 2015-01-28 中信银行 ... 6.95 7.15 1631461285 601998.XSHG 2015-01-29 中信银行 ... 6.90 7.01 930034457 601998.XSHG 2015-01-30 中信银行 ... 6.92 6.95 68146718[8 rows x 8 columns]Order by multiple columns value: secID tradeDate secShortName ... lowestPrice closePrice turnoverVol7 601998.XSHG 2015-01-30 中信银行 ... 6.92 6.95 681467185 601998.XSHG 2015-01-29 中信银行 ... 6.90 7.01 930034451 601998.XSHG 2015-01-28 中信银行 ... 6.95 7.15 1631461284 000001.XSHE 2015-01-09 平安银行 ... 14.71 15.08 2508500233 000001.XSHE 2015-01-08 平安银行 ... 14.90 14.96 140771421[5 rows x 8 columns]
4.3 数据的访问和操作
4.3.1 数据的筛选
已经介绍了使用 loc 、 iloc 、 at 、 iat 以及 [] 访 问 DataFrame 数据的几种方式,这里再介绍一种方法,使用 : 来获取部行或者全部列:
print(df.iloc[1:4][:]) secID tradeDate secShortName ... lowestPrice closePrice turnoverVol1 601998.XSHG 2015-01-28 中信银行 ... 6.95 7.15 1631461282 000001.XSHE 2015-01-07 平安银行 ... 15.30 15.48 1700120673 000001.XSHE 2015-01-08 平安银行 ... 14.90 14.96 140771421
我们可以扩展之前介绍的使用布尔类型的向量获取数据的方法,可以很方便地过滤数据,例如,我们要选出收盘价在均值以上的数据:
print(df[df.closePrice > df.closePrice.mean()].head()) secID tradeDate secShortName ... lowestPrice closePrice turnoverVol0 000001.XSHE 2015-01-05 平安银行 ... 15.60 16.02 2860436432 000001.XSHE 2015-01-07 平安银行 ... 15.30 15.48 1700120673 000001.XSHE 2015-01-08 平安银行 ... 14.90 14.96 1407714214 000001.XSHE 2015-01-09 平安银行 ... 14.71 15.08 2508500236 000001.XSHE 2015-01-06 平安银行 ... 15.55 15.78 216642140[5 rows x 8 columns]
isin() 函数可方便地过滤 DataFrame 中的数据:
print(df[df['secID'].isin(['601998.XSHG'])].head()) secID tradeDate secShortName ... lowestPrice closePrice turnoverVol1 601998.XSHG 2015-01-28 中信银行 ... 6.95 7.15 1631461285 601998.XSHG 2015-01-29 中信银行 ... 6.90 7.01 930034457 601998.XSHG 2015-01-30 中信银行 ... 6.92 6.95 68146718[3 rows x 8 columns]
4.3.2 处理缺失数据
在访问数据的基础上,我们可以更改数据,例如,修改某些元素为缺失值:
df.loc[df['secID'] == '000001.XSHE', 'closePrice'] = np.nanprint(df) secID tradeDate secShortName ... lowestPrice closePrice turnoverVol0 000001.XSHE 2015-01-05 平安银行 ... 15.60 NaN 2860436431 601998.XSHG 2015-01-28 中信银行 ... 6.95 7.15 1631461282 000001.XSHE 2015-01-07 平安银行 ... 15.30 NaN 1700120673 000001.XSHE 2015-01-08 平安银行 ... 14.90 NaN 1407714214 000001.XSHE 2015-01-09 平安银行 ... 14.71 NaN 2508500235 601998.XSHG 2015-01-29 中信银行 ... 6.90 7.01 930034456 000001.XSHE 2015-01-06 平安银行 ... 15.55 NaN 2166421407 601998.XSHG 2015-01-30 中信银行 ... 6.92 6.95 68146718[8 rows x 8 columns]
查找元素缺失的行
print(df[df['closePrice'].isnull()]) secID tradeDate secShortName ... lowestPrice closePrice turnoverVol0 000001.XSHE 2015-01-05 平安银行 ... 15.60 NaN 2860436432 000001.XSHE 2015-01-07 平安银行 ... 15.30 NaN 1700120673 000001.XSHE 2015-01-08 平安银行 ... 14.90 NaN 1407714214 000001.XSHE 2015-01-09 平安银行 ... 14.71 NaN 2508500236 000001.XSHE 2015-01-06 平安银行 ... 15.55 NaN 216642140[5 rows x 8 columns]
原始数据的中很可能存在一些数据的缺失,就如同现在处理的这个样例数据一样, 处理缺失数据有多种方式。通常使用 dataframe.dropna() , dataframe.dropna() 可以按行丢弃带有 nan 的数 据;若指定 how=‘all’ (默认是 ‘any’ ),则只在整行全部是 nan 时丢弃数 据;若指定 thresh ,则表示当某行数据非缺失列数超过指定数值时才保留;要指定根据某列丢弃可以通过 subset 完成。
# 使第一行的 closePrice 为 nandf.loc[1, 'closePrice'] = np.nan# 查看 df 过滤前的大小print("Data size before filtering:")print(df.shape)# 过滤所有含有 nan 的行print("Drop all rows that have any NaN values:")print("Data size after filtering:")print(df.dropna().shape)print(df.dropna())# 仅过滤整行为 nan 的行print("Drop only if all columns are NaN:")print("Data size after filtering:")print(df.dropna(how='all').shape)print(df.dropna(how='all'))# 过滤行当 nan 超过6个时print("Drop rows who do not have at least six values that are not NaN")print("Data size after filtering:")print(df.dropna(thresh=6).shape)print(df.dropna(thresh=6))# 当某一列的值为 nan 时才过滤该行print("Drop only if NaN in specific column:")print("Data size after filtering:")print(df.dropna(subset=['closePrice']).shape)print(df.dropna(subset=['closePrice']))Data size before filtering:(8, 8)Drop all rows that have any NaN values:Data size after filtering:(7, 8) secID tradeDate secShortName ... lowestPrice closePrice turnoverVol0 000001.XSHE 2015-01-05 平安银行 ... 15.60 16.02 2860436432 000001.XSHE 2015-01-07 平安银行 ... 15.30 15.48 1700120673 000001.XSHE 2015-01-08 平安银行 ... 14.90 14.96 1407714214 000001.XSHE 2015-01-09 平安银行 ... 14.71 15.08 2508500235 601998.XSHG 2015-01-29 中信银行 ... 6.90 7.01 930034456 000001.XSHE 2015-01-06 平安银行 ... 15.55 15.78 2166421407 601998.XSHG 2015-01-30 中信银行 ... 6.92 6.95 68146718[7 rows x 8 columns]Drop only if all columns are NaN:Data size after filtering:(8, 8) secID tradeDate secShortName ... lowestPrice closePrice turnoverVol0 000001.XSHE 2015-01-05 平安银行 ... 15.60 16.02 2860436431 601998.XSHG 2015-01-28 中信银行 ... 6.95 NaN 1631461282 000001.XSHE 2015-01-07 平安银行 ... 15.30 15.48 1700120673 000001.XSHE 2015-01-08 平安银行 ... 14.90 14.96 1407714214 000001.XSHE 2015-01-09 平安银行 ... 14.71 15.08 2508500235 601998.XSHG 2015-01-29 中信银行 ... 6.90 7.01 930034456 000001.XSHE 2015-01-06 平安银行 ... 15.55 15.78 2166421407 601998.XSHG 2015-01-30 中信银行 ... 6.92 6.95 68146718[8 rows x 8 columns]Drop rows who do not have at least six values that are not NaNData size after filtering:(8, 8) secID tradeDate secShortName ... lowestPrice closePrice turnoverVol0 000001.XSHE 2015-01-05 平安银行 ... 15.60 16.02 2860436431 601998.XSHG 2015-01-28 中信银行 ... 6.95 NaN 1631461282 000001.XSHE 2015-01-07 平安银行 ... 15.30 15.48 1700120673 000001.XSHE 2015-01-08 平安银行 ... 14.90 14.96 1407714214 000001.XSHE 2015-01-09 平安银行 ... 14.71 15.08 2508500235 601998.XSHG 2015-01-29 中信银行 ... 6.90 7.01 930034456 000001.XSHE 2015-01-06 平安银行 ... 15.55 15.78 2166421407 601998.XSHG 2015-01-30 中信银行 ... 6.92 6.95 68146718[8 rows x 8 columns]Drop only if NaN in specific column:Data size after filtering:(7, 8) secID tradeDate secShortName ... lowestPrice closePrice turnoverVol0 000001.XSHE 2015-01-05 平安银行 ... 15.60 16.02 2860436432 000001.XSHE 2015-01-07 平安银行 ... 15.30 15.48 1700120673 000001.XSHE 2015-01-08 平安银行 ... 14.90 14.96 1407714214 000001.XSHE 2015-01-09 平安银行 ... 14.71 15.08 2508500235 601998.XSHG 2015-01-29 中信银行 ... 6.90 7.01 930034456 000001.XSHE 2015-01-06 平安银行 ... 15.55 15.78 2166421407 601998.XSHG 2015-01-30 中信银行 ... 6.92 6.95 68146718[7 rows x 8 columns]
有数据缺失时也未必是全部丢弃, dataframe.fillna(value=value) 可以指定填补缺失值的数值
print(df.fillna(value=20150101).head()) secID tradeDate secShortName ... lowestPrice closePrice turnoverVol0 000001.XSHE 2015-01-05 平安银行 ... 15.60 16.02 2860436431 601998.XSHG 2015-01-28 中信银行 ... 6.95 20150101.00 1631461282 000001.XSHE 2015-01-07 平安银行 ... 15.30 15.48 1700120673 000001.XSHE 2015-01-08 平安银行 ... 14.90 14.96 1407714214 000001.XSHE 2015-01-09 平安银行 ... 14.71 15.08 250850023[5 rows x 8 columns]
4.3.3 数据操作
Series 和 DataFrame 的类函数提供了一些函数,如 mean() 、 sum() 等,指定0按列进行,指定1按行进行:
print(df.mean(0))openPrice 1.236375e+01highestPrice 1.268125e+01lowestPrice 1.210375e+01closePrice 1.230375e+01turnoverVol 1.735769e+08dtype: float64
value_counts 函数可以方便地统计频数:
print(df['closePrice'].value_counts().head())16.02 17.15 115.48 114.96 115.08 1Name: closePrice, dtype: int64
在 panda 中, Series 可以调用 map 函数来对每个元素应用一个函数, DataFrame 可以调用 apply 函数对每一列(行)应用一个函 数, applymap 对每个元素应用一个函数。这里面的函数可以是用户自定义的一个 lambda函数,也可以是已有的其他函数。下例展示了将收盘价调整到 [0, 1] 区 间:
print(df[['closePrice']].apply(lambda x: (x - x.min()) / (x.max() - x.min())).head()) closePrice0 1.0000001 0.0220512 0.9404633 0.8831314 0.896362
使用 append 可以在 Series 后添加元素,以及在 DataFrame 尾部添加一行:
dat1 = df[['secID', 'tradeDate', 'closePrice']].head()dat2 = df[['secID', 'tradeDate', 'closePrice']].iloc[2]print("Before appending:")print(dat1)dat = dat1.append(dat2, ignore_index=True)print("After appending:")print(dat)Before appending: secID tradeDate closePrice0 000001.XSHE 2015-01-05 16.021 601998.XSHG 2015-01-28 7.152 000001.XSHE 2015-01-07 15.483 000001.XSHE 2015-01-08 14.964 000001.XSHE 2015-01-09 15.08After appending: secID tradeDate closePrice0 000001.XSHE 2015-01-05 16.021 601998.XSHG 2015-01-28 7.152 000001.XSHE 2015-01-07 15.483 000001.XSHE 2015-01-08 14.964 000001.XSHE 2015-01-09 15.085 000001.XSHE 2015-01-07 15.48
DataFrame 可以像在SQL中一样进行合并,在上篇中,我们介绍了使 用 concat 函数创建 DataFrame ,这就是一种合并的方式。另外一种方式使 用 merge 函数,需要指定依照哪些列进行合并,下例展示了如何根据security ID 和交易日合并数据:
dat1 = df[['secID', 'tradeDate', 'closePrice']]dat2 = df[['secID', 'tradeDate', 'turnoverVol']]dat = dat1.merge(dat2, on=['secID', 'tradeDate'])print("The first DataFrame:")print(dat1.head())print("The second DataFrame:")print(dat2.head())print("Merged DataFrame:")print(dat.head())The first DataFrame: secID tradeDate closePrice0 000001.XSHE 2015-01-05 16.021 601998.XSHG 2015-01-28 7.152 000001.XSHE 2015-01-07 15.483 000001.XSHE 2015-01-08 14.964 000001.XSHE 2015-01-09 15.08The second DataFrame: secID tradeDate turnoverVol0 000001.XSHE 2015-01-05 2860436431 601998.XSHG 2015-01-28 1631461282 000001.XSHE 2015-01-07 1700120673 000001.XSHE 2015-01-08 1407714214 000001.XSHE 2015-01-09 250850023Merged DataFrame: secID tradeDate closePrice turnoverVol0 000001.XSHE 2015-01-05 16.02 2860436431 601998.XSHG 2015-01-28 7.15 1631461282 000001.XSHE 2015-01-07 15.48 1700120673 000001.XSHE 2015-01-08 14.96 1407714214 000001.XSHE 2015-01-09 15.08 250850023
参数 | 作用 |
---|---|
left | 拼接的左侧DataFrame对象 |
right | 拼接的右侧DataFrame对象 |
on | 要加入的列或索引级别名称。 必须在左侧和右侧DataFrame对象中找到。 如果未传递且left_index和right_index为False,则DataFrame中的列的交集将被推断为连接键 |
left_on | 左侧DataFrame中的列或索引级别用作键。 可以是列名,索引级名称,也可以是长度等于DataFrame长度的数组 |
right_on | 左侧DataFrame中的列或索引级别用作键。 可以是列名,索引级名称,也可以是长度等于DataFrame长度的数组 |
left_index | 如果为True,则使用左侧DataFrame中的索引(行标签)作为其连接键。 对于具有MultiIndex(分层)的DataFrame,级别数必须与右侧DataFrame中的连接键数相匹配 |
right_index | 与left_index功能相似 |
how | 值有‘left’,‘right’,‘outer’,‘inner’,默认inner。inner是取交集,outer取并集,left保留左边所有行列数据并使用右边的数据进行补全,right与left相反 |
sort | 按字典顺序通过连接键对结果DataFrame进行排序。 默认为True,设置为False将在很多情况下显着提高性能 |
suffixes | 用于重叠列的字符串后缀元组。 默认为(‘x’,’ y’) |
copy | 始终从传递的DataFrame对象复制数据(默认为True),即使不需要重建索引也是如此 |
DataFrame 另一个强大的函数是 groupby ,可以十分方便地对数据分组处理, 我们对2015年一月内十支股票的开盘价,最高价,最低价,收盘价和成交量求平均 值:
df_grp = df.groupby('secID')grp_mean = df_grp.mean()print(grp_mean) openPrice highestPrice lowestPrice closePrice turnoverVolsecID 000001.XSHE 15.560000 15.988 15.212000 15.464000 2.128639e+08601998.XSHG 7.036667 7.170 6.923333 7.036667 1.080988e+08
如果希望取每只股票的最新数据,应该怎么操作呢? drop_duplicates 可以实现这个功能,首先对数据按日期排序,再按security ID去重:
df2 = df.sort_values(by=['secID', 'tradeDate'], ascending=[True, False])print(df2.drop_duplicates(subset='secID')) secID tradeDate secShortName ... lowestPrice closePrice turnoverVol4 000001.XSHE 2015-01-09 平安银行 ... 14.71 15.08 2508500237 601998.XSHG 2015-01-30 中信银行 ... 6.92 6.95 68146718[2 rows x 8 columns]
若想要保留最老的数据,可以在降序排列后取最后一个记录,通过指定 keep=‘last’ (默认取第一条记录)可以实现:
print(df2.drop_duplicates(subset='secID', keep='last')) secID tradeDate secShortName ... lowestPrice closePrice turnoverVol0 000001.XSHE 2015-01-05 平安银行 ... 15.60 16.02 2860436431 601998.XSHG 2015-01-28 中信银行 ... 6.95 7.15 163146128[2 rows x 8 columns]
4.3.4 数据可视化
pandas 数据直接可以绘图查看,下例中我们采用中国石化一月的收盘价进行绘 图,其中 set_index(‘tradeDate’)[‘closePrice’] 表示 将 DataFrame 的 ‘tradeDate’ 这一列作为索引,将 ‘closePrice’ 这一列作 为 Series 的值,返回一个 Series 对象,随后调用 plot 函数绘图,更多的参 数可以在 matplotlib 的文档中查看。
dat = df[df['secID'] == '600028.XSHG'].set_index('tradeDate')['closePrice']dat.plot(title="Close Price of SINOPEC (600028) during Jan, 2015")
五、代码简介
类型 | 描述 | 代码 | 注释 |
数据创建 | 创建 DataFrame | df = DataFrame({'one': [1, 2, 3], 'two': ['a', 'b', 'c']}, index=['a', 'b', 'c']) | index:行名 columns:列名 |
df = DataFrame([[1, 2, 3], ['a', 'b', 'c']], columns=['one', 'two', 'three']) | |||
创建日期 Series | pd.date_range('20210101', periods=5) | periods:递增数量 | |
拼接 Series | df = pd.concat([Series([1, 2, 3]), Series(['a', 'b', 'c'])], axis=1) | asxi:等于1时横向拼接,等于0时纵向拼接 | |
数据基础访问 | 查看数据大小 | df.shape | - |
查看前n行 | df.head(n) | 默认为5 | |
查看后n行 | df.tail(n) | ||
查看纯数值数据的统计信息 | df.describe() | 包括计数,平均值,标准差,最小值,最大值 | |
访问列 | df['one'] | []:访问列'one',类型为Series | |
df[['one']] df[['one', 'two']] | [[]]:访问列'one',类型为DataFrame | ||
访问行 | df.iloc[0] df.iloc[0:2] | iloc:按下标选取行 loc:按行名选取行 注:行列括一起选取的是值,分开选取的是DataFrame | |
df.loc['a'] | |||
访问行+列 | df.loc[['a', 'b']][['one']] df.iloc[0:2][['one']] | ||
访问值 | df.iloc[0, 0] df.loc['a', 'one'] | ||
数据筛选 | 选取符合等式的行 | df[df['one'] > 1] | 选取列'one'的值大于1的行 |
选取含有某字符串的行 | df[df['two'].str.contains('a')] | 选取列'two'含有字符串'a'的行 | |
选取属于某几个值的行 | df[df['one'].isin([1, 2])] | 选取列'one'的值属于[1, 2]的行 | |
选取缺失行 | df[df['one'].isnull()] | 选取列'one’值缺失的行 | |
选取非缺失行 | df[df['one'].notnull()] | 选取列'one’值非缺失的行 | |
数据统计 | 求均值 | df.mean(0) | 指定0按列进行,指定1按行进行 |
求和 | df.sum(0) | ||
统计频数 | df['one'].value_counts() | 列'one'中每个值出现的次数 | |
数据操作 | 按列(行)名排序 | df.sort_index(axis=1, ascending=True) | axis:等于1时按列名排序,等于0时按行名排序 ascending:为True时升序,为False时降序 |
按数值排序 | df.sort_values(by=['one', 'two'], ascending=[True, True]) | by:排序的列名 | |
数据去重 | df.drop_duplicates(subset=['one']) | 根据列'one'去重,仅保留第一次出现的行 | |
数据合并 | df.merge(df2, on=['one']) | on:作为合并键值的列名 how:值有‘left’,‘right’,‘outer’,‘inner’,默认inner。inner是取交集,outer取并集,left保留左边所有行列数据并使用右边的数据进行补全,right与left相反 suffixes:用于重叠列的列名重命名,默认为(‘_x’,‘_y’) | |
对每列(行)应用一个函数 | df[['one']].apply(lambda x: x+1) | lambda:自定义函数 | |
对每个元素应用一个函数 | df[['one']].applymap(lambda x: x+1) | ||
添加数据 | df.append(df2, ignore_index=True) | ignore_index:为True时将行名重定义为有序数字,为False将原行名拼接 | |
删除列 | df.drop('one', axis=1) | - | |
数据分组 | df.groupby('one') | 根据列'one'对数据进行分组,分组后是一个DataFrameGroupBy对象,如想查看具体内容,可使用list转化为列表 | |
将所有元素转化为字符串 | df.astype(str) | - | |
将列元素转化为字符串 | df['one'].astype(str) | - | |
将列元素根据逗号拆分 | df.drop('two', axis=1).join(df['two'].str.split(',', expand=True).stack().reset_index(level=1, drop=True).rename('tag')) | rename:拆分后的列名 |
六、Pandas 与 Excel 的交互
5.1 读取 Excel 数据
将 Excel 表中的数据解析为 DataFrame
import pandas as pdwith pd.ExcelFile('saw.xlsx') as xlsx: # 列出表名 names = xlsx.sheet_names # 读取表 Sheet1 的数据 df = pd.read_excel(xlsx, 'Sheet1', na_value=['NA'], keep_default_na=False) # na_value:把值解释为 nan # keep_default_na:把空字符串解析为空字符串'',默认为True时解析为 nan # 另外 index_col=0 可以把第一列设置为索引
5.2 写入 Excel 数据
将数据 DataFrame 写入到 Excel 表中
import pandas as pdfrom openpyxl import load_workbookwb = load_workbook('saw.xlsx')with pd.ExcelWriter('saw.xlsx', engine='openpyxl') as xlsx: # 将数据 df 写入到表格 Sheet2 中 xlsx.book = wb # 不保留索引和列名 df.to_excel(xlsx, sheet_name='Sheet2', index=False, header=None)
注:直接调用 ExcelWriter 方法写入的话,原有的表会被覆盖,所以需要结合 openpyxl 模块进行写入
七、参考文章
《Python 股票量化交易教程》