一、Pandas索引
1.行索引
#创建Series
s=np.Series(np.random.rand(5),index=list('abcde'))
#对行索引命名
s.index.name='alpha'
#获取行索引
s.index
2.列索引
df = pd.DataFrame(np.random.randn(4, 6), index=list('ADFH'), columns=['one', 'two', 'three', 'four', 'five', 'six'])
#行索引
df.index
#列索引
df.columns
3.索引类
pandas 中有许多内置索引,其中包括:
- pd.CategoricalIndex
- pd.DatetimeIndex
- pd.Float64Index
- pd.Index
- pd.Int64Index
- pd.IntervalIndex
- pd.MultiIndex
- pd.PeriodIndex
- pd.RangeIndex
- pd.TimedeltaIndex
- pd.UInt64Index
4.重复索引
可创建索引值重复的索引:
s=pd.Series(np.arrange(6),index=list('abcdea')
对于重复的索引值,依旧返回Series;
s['a']
对于非重复的索引值,则返回索引到的数据;
s['c']
- 判断 Series 中是否为唯一索引
s.index.is_unique
- 返回所有非重复的索引值的列表
s.index.unique()
5.多层索引
-
层次化索引
可以使数据在一个轴上有多个索引级别。即可以用二维的数据表达更高维度的数据,使数据组织方式更清晰。它使用 pd.MultiIndex 类来表示。 -
层次化索引有什么作用?
比如我们在分析股票数据,我们的一级行索引可以是日期;二级行索引可以是股票代码,列索引可以是股票的交易量,开盘价,收盘价等等。这样我们就可以把多个股票放在同一个时间维度下进行考察和分析。
1)创建
- Series多层索引的创建
[in]:
a = [['a', 'a', 'a', 'b', 'b', 'c', 'c'], [1, 2, 3, 1, 2, 2, 3]]
tuples = list(zip(*a))
tuples
[out]:
[('a', 1), ('a', 2), ('a', 3), ('b', 1), ('b', 2), ('c', 2), ('c', 3)]
[in]:
index = pd.MultiIndex.from_tuples(tuples, names=['first', 'second'])
index
[out]:
MultiIndex(levels=[[u'a', u'b', u'c'], [1, 2, 3]],
labels=[[0, 0, 0, 1, 1, 2, 2], [0, 1, 2, 0, 1, 1, 2]],
names=[u'first', u'second'])
[in]:
s = pd.Series(np.random.randn(7), index=index)
s
[out]:
first second
a 1 -1.192113
2 0.226627
3 0.390052
b 1 0.045297
2 1.552926
c 2 0.014007
3 -0.257103
dtype: float64
由此就创建了一个多层索引数据
第一层索引’first’的索引值为a.b.c;
第二层索引‘second’的索引值为 1.2.3;
Series多层索引的使用:
例 1:
[in]:
s['b']
[out]:
second
1 0.045297
2 1.552926
dtype: float64
例 2:
[in]:
s['b':'c']
[out]:
first second
b 1 0.045297
2 1.552926
c 2 0.014007
3 -0.257103
dtype: float64
- DataFrame 多层索引的创建:
[in]:
df = pd.DataFrame(np.random.randint(1, 10, (4, 3)),
index=[['a', 'a', 'b', 'b'], ['A', 'B', 'A', 'B']],
columns=[['one', 'one', 'two'], ['blue', 'red', 'blue']])
df.index.names = ['row-1', 'row-2']
df.columns.names = ['col-1', 'col-2']
df
DataFrame多层索引的使用:
[in]:
df.loc['a','B']
[out]:
col-1 col-2
one blue 7
red 2
two blue 4
Name: (a, B), dtype: int64
2)索引交换
[in]:
df2 = df.swaplevel('row-1', 'row-2')
交换前:
交换后:
3)按照索引层次进行统计
#按照一级索引进行求和
df.sum(level=0)
#按照二级索引进行求和
df.sum(level=1)
4)索引与列的转换
创建数据框:
[in]:
df = pd.DataFrame({
'a': range(7),
'b': range(7, 0, -1),
'c': ['one', 'one', 'one', 'two', 'two', 'two', 'two'],
'd': [0, 1, 2, 0, 1, 2, 3]
})
df
[out]:
a b c d
0 0 7 one 0
1 1 6 one 1
2 2 5 one 2
3 3 4 two 0
4 4 3 two 1
5 5 2 two 2
6 6 1 two 3
- 将数据框中某些列设置成索引值:
[in]:
df.set_index('c')
[out]:
a b d
c
one 0 7 0
one 1 6 1
one 2 5 2
two 3 4 0
two 4 3 1
two 5 2 2
two 6 1 3
- 将多个列设置成索引值:
[in]:
df2 = df.set_index(['c', 'd'])
df2
[out]:
a b
c d
one 0 0 7
1 1 6
2 2 5
two 0 3 4
1 4 3
2 5 2
3 6 1
- 将多级索引重新转换为默认一级索引:
[in]:
df3 = df2.reset_index().sort_index('columns')
df3
[out]:
a b c d
0 0 7 one 0
1 1 6 one 1
2 2 5 one 2
3 3 4 two 0
4 4 3 two 1
5 5 2 two 2
6 6 1 two 3
二、分组计算
分组计算三步曲:拆分 -> 应用 -> 合并
- 拆分:根据什么进行分组?
- 应用:每个分组进行什么样的计算?
- 合并:把每个分组的计算结果合并起来。
1.对 Series 分组
创建一个 DataFrame:
df = pd.DataFrame({'key1': ['a', 'a', 'b', 'b', 'a'],
'key2': ['one', 'two', 'one', 'two', 'one'],
'data1': np.random.randint(1, 10, 5),
'data2': np.random.randint(1, 10, 5)})
df
[in]:
df['data1'].groupby([df['key1'],df['key2']]).mean()
[out]:
key1 key2
a one 2
two 5
b one 4
two 3
2.对 DataFrame 分组
[in]:
df.groupby('key1').mean()
[out]:
data1 data2
key1
a 3.0 6.666667
b 3.5 7.000000
#key2 不在分组计算范围内,因为它不是可计算的数据。
3.分组中的元素个数统计
[in]:
df.groupby(['key1', 'key2']).size()
[out]:
key1 key2
a one 2
two 1
b one 1
two 1
4.对分组进行迭代
#对 key1 进行分组迭代
[in]:
for name, group in df.groupby('key1'):
print name
print group
[out]:
a
data1 data2 key1 key2
0 1 6 a one
1 5 9 a two
4 3 5 a one
b
data1 data2 key1 key2
2 4 7 b one
3 3 7 b two
5.分组转化为字典
d = dict(list(df.groupby('key1')))
d
6.按列分组
grouped = df.groupby(df.dtypes, axis=1)
7.通过字典进行分组
#创建数据框#
df = pd.DataFrame(np.random.randint(1, 10, (5, 5)),
columns=['a', 'b', 'c', 'd', 'e'],
index=['Alice', 'Bob', 'Candy', 'Dark', 'Emily'])
df.ix[1, 1:3] = np.NaN
#按照列名进行字典映射
mapping = {'a': 'red', 'b': 'red', 'c': 'blue', 'd': 'orange', 'e': 'blue'}
#对字典映射进行分组
grouped = df.groupby(mapping, axis=1)
[out]:
blue orange red
Alice 16 9 9
Bob 8 6 8
Candy 11 6 6
Dark 17 4 9
Emily 14 6 12
8.通过函数分组
df = pd.DataFrame(np.random.randint(1, 10, (5, 5)),
columns=['a', 'b', 'c', 'd', 'e'],
index=['Alice', 'Bob', 'Candy', 'Dark', 'Emily'])
[out]:
a b c d e
Alice 7 9 1 9 1
Bob 6 6 7 1 5
Candy 7 8 5 3 8
Dark 3 4 6 8 1
Emily 1 2 2 1 2
- 当函数作为分组依据时,数据表里的每个索引(可以是行索引,也可以是列索引)都会调用一次函数,函数的返回值作为分组的索引,即相同的返回值分在同一组。
#这里返回值为原索引,故会把原索引分在一起
def _dummy_group(idx):
print idx
return idx
df.groupby(_dummy_group)
#这里返回的是原索引的字母的长度,故会把相同字符长度的的分在一起
def _dummy_group(idx):
print idx
return len(idx)
df.groupby(_dummy_group)
9.通过索引级别进行分组
columns = pd.MultiIndex.from_arrays([['China', 'USA', 'China', 'USA', 'China'],
['A', 'A', 'B', 'C', 'B']], names=['country', 'index'])
df = pd.DataFrame(np.random.randint(1, 10, (5, 5)), columns=columns)
df
[out]:
country China USA China USA China
index A A B C B
0 9 6 9 6 2
1 5 6 1 8 7
2 2 5 4 5 2
3 4 8 9 4 9
4 7 2 9 1 8
- 根据列索引的country进行分组计算:
df.groupby(level='country', axis=1).count()
df.groupby(level='country', axis=1).sum()
三、聚合运算
分组运算,先根据一定规则拆分后的数据,然后对数据进行聚合运算,如前面见到的 mean(), sum() 等就是聚合的例子。聚合时,拆分后的第一个索引指定的数据都会依次传给聚合函数进行运算。最后再把运算结果合并起来,生成最终结果。
聚合函数除了内置的 sum(), min(), max(), mean() 等等之外,还可以自定义聚合函数。自定义聚合函数时,使用 agg() 或 aggregate() 函数。
1.数据聚合
1)内置聚合函数
#创建数据
df = pd.DataFrame({'key1': ['a', 'a', 'b', 'b', 'a'],
'key2': ['one', 'two', 'one', 'two', 'one'],
'data1': np.random.randint(1, 10, 5),
'data2': np.random.randint(1, 10, 5)})
df
#pandas的各种内置函数
df.groupby('key1').sum()
df.groupby('key1').mean()
df.groupby('key1').max()
df.groupby('key1').min()
df.groupby('key1').describe()
2)自定义聚合函数 agg
#创建自定义函数【最大值-最小值】
def peak_verbose(s):
print type(s)
return s.max() - s.min()
#按照 key1 进行聚合得到分组数据
grouped = df.groupby('key1')
#对组内数据应用函数
grouped.agg(peak_verbose)
3)一次性应用多个聚合函数
#对 data1、data2 分别应用内置 mean函数、std 函数、自定义 peak 函数;
def peak(s):
return s.max() - s.min()
grouped['data1', 'data2'].agg(['mean', 'std', peak])
# 给聚合后的列取名
grouped['data1'].agg([('agerage', 'mean'), ('max-range', peak)])
4)不同的列应用不同聚合函数
使用 dict 字典作为参数来是实现:
d={'data1':['mean',peak,'max','min'],'data2':[peak]}
grouped.agg(d)
5)重置索引
grouped.agg(d).reset_index()
#或者
df.groupby('key1', as_index=False).agg(d)
2.分组运算和转换
一般作数据分组时,会改变原来数据框的形态,但一般用于数据处理时,会将分组后的数据添加至源数据末尾,传统的处理方法如下所示:
#创建数据框
df = pd.DataFrame({'key1': ['a', 'a', 'b', 'b', 'a'],
'key2': ['one', 'two', 'one', 'two', 'one'],
'data1': np.random.randint(1, 10, 5),
'data2': np.random.randint(1, 10, 5)})
# 给 df 每行都添加一个以 key1 分组后的平均值,add_prefix为在原分组名前添加括号内参数
k1_mean = df.groupby('key1').mean().add_prefix('mean_')
#再与原表进行数据连接
pd.merge(df, k1_mean, left_on='key1', right_index=True)
1)分组数据变换 transform
k1_mean = df.groupby('key1').transform(np.mean).add_prefix('mean_')
#再添加至原表中
df[k1_mean.columns]=k1_mean
2)自定义数据处理 apply
创建数据框:
df = pd.DataFrame({'key1': ['a', 'a', 'b', 'b', 'a', 'a', 'a', 'b', 'b', 'a'],
'key2': ['one', 'two', 'one', 'two', 'one', 'one', 'two', 'one', 'two', 'one'],
'data1': np.random.randint(1, 10, 10),
'data2': np.random.randint(1, 10, 10)})
创建一个函数,根据 column 参数进行排序,输出其最大的 n 行数据:
def top(df, n=2, column='data1'):
return df.sort_values(by=column, ascending=False)[:n]
对分组数据应用 top 函数:
df.groupby('key1').apply(top,n=3,columns='data2')
- apply 应用示例:用不同的分组平均值填充空缺数据
#创建一组带有残缺值的数据
states = ['Ohio', 'New York', 'Vermont', 'Florida',
'Oregon', 'Nevada', 'California', 'Idaho']
group_key = ['East'] * 4 + ['West'] * 4
data = pd.Series(np.random.randn(8), index=states)
data[['Vermont', 'Nevada', 'Idaho']] = np.nan
#获取分组的平均值
data.groupby(group_key).mean()
fill_mean = lambda g: g.fillna(g.mean())
data.groupby(group_key).apply(fill_mean)
四、数据 IO(Input/Output)
载入数据到 Pandas
- 索引:将一个列或多个列读取出来构成 DataFrame,其中涉及是否从文件中读取索引以及列名
- 类型推断和数据转换:包括用户自定义的转换以及缺失值标记
- 日期解析
- 迭代:针对大文件进行逐块迭代。这个是Pandas和Python原生的csv库的最大区别
- 不规整数据问题:跳过一些行,或注释等等
1.索引及列名
#读取数据
df = pd.read_csv('data/ex1.csv')
#根据原始数据,按指定分隔符进行分割
df = pd.read_csv('data/ex1.csv', sep=',')
#列名缺失进行补充
pd.read_csv('data/ex2.csv', header=None,names=['a', 'b', 'c', 'd', 'msg'])
#指定行索引
pd.read_csv('data/ex2.csv', header=None, names=['a', 'b', 'c', 'd', 'msg'], index_col='msg')
#指定多层行索引
pd.read_csv('data/ex2.csv', header=None, names=['a', 'b', 'c', 'd', 'msg'], index_col=['msg', 'a'])
- 处理不规则的分隔符
该数据中存在不规则的分隔符,列名分隔符为多个空格,而数据的分隔符为单个空格或两个空格,此处要用到正则表达式:
#\s为空格,后面跟+代表1个到多个空格
pd.read_table('data/ex3.csv', sep='\s+')
2.缺失值处理
读取数据时可指定缺失值
#此处将 message 列中的 foo、NA 指定为缺失值;而 something 中的 two 指定为缺失值;
pd.read_csv('data/ex5.csv', na_values={'message': ['foo', 'NA'], 'something': ['two']})
3.逐块读取数据
对于数据量比较庞大的时候,而我们只需要对数据一部分进行处理,可以使用 pandas 中的逐块读取数据方法,逐块进行计算,来增加计算速度。
例如:我们要对下述数据的 key 进行次数统计:
pd.read_csv('data/ex6.csv', nrows=10)
[out]:
#该数据有万余行
one two three four key
0 0.467976 -0.038649 -0.295344 -1.824726 L
1 -0.358893 1.404453 0.704965 -0.200638 B
2 -0.501840 0.659254 -0.421691 -0.057688 G
3 0.204886 1.074134 1.388361 -0.982404 R
4 0.354628 -0.133116 0.283763 -0.837063 Q
5 1.817480 0.742273 0.419395 -2.251035 Q
6 -0.776764 0.935518 -0.332872 -1.875641 U
7 -0.913135 1.530624 -0.572657 0.477252 K
8 0.358480 -0.497572 -0.367016 0.507702 S
9 -1.740877 -1.160417 -1.637830 2.172201 G
#对数据逐块进行读取
tr = pd.read_csv('data/ex6.csv', chunksize=1000)
#创建一个空序列表,命名为 key_count
key_count = pd.Series([])
#对 tr 中的每个数据块,以 key 进行相加,最后按照 key_count的大小降序排列
for pieces in tr:
key_count = key_count.add(pieces['key'].value_counts(), fill_value=0)
key_count = key_count.sort_values(ascending=False)
4.保存数据到磁盘
#默认保存至磁盘方法
df.to_csv('data/ex5_out.csv')
# 不写索引
df.to_csv('data/ex5_out.csv', index=False)
# 不写列名称
df.to_csv('data/ex5_out.csv', header=None)
# 指定分隔符
df.to_csv('data/ex5_out.csv', sep='|')
# 只写出一部分列
df.to_csv('data/ex5_out.csv', columns=['a', 'b', 'message'])
5.二进制数据
二进制的优点是容量小,读取速度快。缺点是可能在不同版本间不兼容。比如 Pandas 版本升级后,早期版本保存的二进制数据可能无法正确地读出来。
pd.to_pickle(df, 'data/ex1_pickle.bin')
6.其他格式简介
- HDF5: HDF5是个C语言实现的库,可以高效地读取磁盘上的二进制存储的科学数据;
- Excel文件: pd.read_excel/pd.ExcelFile/pd.ExcelWriter;
- JSON: 通过 json 模块转换为字典,再转换为 DataFrame;
- SQL 数据库:通过 pd.io.sql 模块来从数据库读取数据;
- NoSQL (MongoDB) 数据库:需要结合相应的数据库模块,如 pymongo ,再通过游标把数据读出来,转换为 DataFrame;
五、时间序列
- 时间戳 tiimestamp:固定的时刻 -> pd.Timestamp
- 固定时期 period:比如 2016年3月份,再如2015年销售额 -> pd.Period
- 时间间隔 interval:由起始时间和结束时间来表示,固定时期是时间间隔的一个特殊
1.python 里的 datetime
[in]:
from datetime import datetime
from datetime import timedelta
now = datetime.now()
[out]:
datetime.datetime(2016, 4, 28, 14, 49, 51, 307000)
- 字符串和 datetime 转换
%y 两位数的年份表示(00-99)
%Y 四位数的年份表示(000-9999)
%m 月份(01-12)
%d 月内中的一天(0-31)
%H 24小时制小时数(0-23)
%I 12小时制小时数(01-12)
%M 分钟数(00-59)
%S 秒(00-59)
%a 本地简化星期名称
%A 本地完整星期名称
%b 本地简化的月份名称
%B 本地完整的月份名称
%c 本地相应的日期表示和时间表示
%j 年内的一天(001-366)
%p 本地A.M.或P.M.的等价符
%U 一年中的星期数(00-53)星期天为星期的开始
%w 星期(0-6),星期天为星期的开始
%W 一年中的星期数(00-53)星期一为星期的开始
%x 本地相应的日期表示
%X 本地相应的时间表示
%Z 当前时区的名称
%% %号本身
2.Pandas 里的时间序列
创建一个以时间为序列的数据
dates = [datetime(2020, 3, 1), datetime(2020, 3, 2), datetime(2016, 3, 3), datetime(2016, 3, 4)]
s = pd.Series(np.random.randn(4), index=dates)
[out]:
2020-03-01 0.300996
2020-03-02 -1.251884
2016-03-03 -0.324382
2016-03-04 -0.563142
3.日期范围
1)生成日期范围
#规定开始日期和结束日期
pd.date_range('20200320', '20200331')
#规定元素个数
pd.date_range(start='20200320', periods=10)
#规则化时间戳
pd.date_range(start='2020-03-20 16:23:32', periods=10, normalize=True)
2)时间频率
#设定时间频率
pd.date_range(start='2020-03-20 16:23:32', periods=10, freq='D')
#可对 freq 规定时间频率
4.时期及算术运算
1)时期序列时期的频率
- A-DEC: 以 12 月份作为结束的年时期
- A-NOV: 以 11 月份作为结束的年时期
- Q-DEC: 以 12 月份作为结束的季度时期
具体表示如下:
2)转换季度时间频率
p = pd.Period('2020Q4', 'Q-JAN')
#Q-JAN表示以 1 月份结束的财年季度时间,则 2020Q4 代表 2019-11-1 到 2020-1-31
3)Timestamp 和 Period 相互转换
#创建一个 Series 数据,index为2020年1月开始以月为单位的时间序列;
[in]:
ts = pd.Series(np.random.randn(5), index = pd.date_range('2020-01-01', periods=5, freq='M'))
[out]:
2020-12-29 -0.049194
2020-12-30 -1.370552
2020-12-31 0.566372
2021-01-01 -1.501879
2021-01-02 -0.120327
#将其转化为 Period,会丢失日期细节
ts.to_period()
[out]:
2020-12 -0.049194
2020-12 -1.370552
2020-12 0.566372
2021-01 -1.501879
2021-01 -0.120327
5.重采样
- 高频率 -> 低频率 -> 降采样:
例如根据每分钟股票交易数据,获得每5 分钟股票交易数据,或者转换为日交易数据
ts = pd.Series(np.random.randint(0, 50, 60), index=pd.date_range('2020-03-25 09:30', periods=60, freq='T'))
# 0-4 分钟为第一组,默认是以起始时间为重采样后的时间标签
ts.resample('5min', how='sum')
#设置标签以结束时间
ts.resample('5min', how='sum', label='right')
- 低频率 -> 高频率 -> 升采样
1)OHLC 重采样
OHLC=Open/High/Low/Close
#创建一个模拟股票数据
ts = pd.Series(np.random.randint(0, 50, 60), index=pd.date_range('2020-03-25 09:30', periods=60, freq='T'))
#对该股票故事以每 5 分钟的开盘价、收盘价、最高价、最低价进行重采样
ts.resample('5min', how='ohlc')
[out]:
open high low close
2016-04-25 09:30:00 18 49 5 5
2016-04-25 09:35:00 12 47 6 37
2016-04-25 09:40:00 44 44 8 12
2016-04-25 09:45:00 26 38 5 26
2016-04-25 09:50:00 39 39 6 24
2016-04-25 09:55:00 0 27 0 26
2016-04-25 10:00:00 27 27 18 25
2016-04-25 10:05:00 35 48 3 48
2016-04-25 10:10:00 5 48 2 11
2016-04-25 10:15:00 18 43 10 10
2016-04-25 10:20:00 5 45 3 3
2016-04-25 10:25:00 24 46 2 25
2)通过 groupby 重采样
ts = pd.Series(np.random.randint(0, 50, 100), index=pd.date_range('2016-03-01', periods=100, freq='D'))
ts.groupby(lambda x: x.month).sum()
[out]:
3 759
4 648
5 748
6 172
ts.groupby(ts.index.to_period('M')).sum()
[out]:
2020-03 693
2020-04 722
2020-05 804
2020-06 188
3)升采样和插值
#创建一个时间序列,以周为单位,每周五进行采样
df = pd.DataFrame(np.random.randint(1, 50, 2), index=pd.date_range('2020-03-27', periods=2, freq='W-FRI'))
[out]:
0
2020-03-27 46
2020-04-03 34
df.resample('D',fill_method='ffill', limit=3)
[out]:
0
2020-03-27 13.0
2020-03-28 13.0
2020-03-29 13.0
2020-03-30 13.0
2020-03-31 NaN
2020-04-01 NaN
2020-04-02 NaN
2020-04-03 49.0
4)时期重采样
df = pd.DataFrame(np.random.randint(2, 30, (24, 4)),
index=pd.period_range('2015-01', '2016-12', freq='M'),
columns=list('ABCD'))
#以每年12月结束的时间进行重采样
adf = df.resample('A-DEC', how='mean')
5)时间日期解析
#导入一个时间序列数据
df = pd.read_csv('data/002001.csv', index_col='Date')
#但此时 index 并不是时间格式,而是 object 格式,导入数据时添加参数parse_dates=True,可对时间数据进行解析
df = pd.read_csv('data/002001.csv', index_col='Date', parse_dates=True)