NumPy虽然提供了方便的数组处理功能,但是它还是缺少许多数据处理、分析所需的一些快速工具。
Pandas基于NumPy构建,提供众多更高级的数据处理功能,使得以 NumPy 为中心的数据处理工作更便捷。
pandas的作者在设计 Pandas 时,主要考虑以下几个方面:
按轴自动或显式数据对齐功能的数据结构
来自不同数据源的数据,由于各数据源的索引方式不同,经常会出现数据未对齐,而这会导致很多常见错误。
集成时间序列功能
既能处理时间序列数据也能处理非时间序列数据的数据结构
数学运算和简约(比如对某个轴求和)可以根据不同的元数据(轴编号)执行
灵活处理缺失数据
合并及其他出现在常见数据库(例如基于SQL的)中的关系型运算
引入 Pandas 的约定为:
1 | from pandas import Series, DataFrame |
本章内容较多,每节的内容拆分成一个 ipython notebook:
数据结构
Pandas中两个主要的数据结构是 Series 和 DataFrame,两个数据结构中都使用了索引(Index)对象。
%pylab inline
from pandas import Series, DataFrame
import pandas as pd
《利用Python进行数据分析》 作者: Wes McKinney 译者: 唐学韬
Pandas中两个主要的数据结构是 Series
和 DataFrame
,两个数据结构中都使用了索引(Index
)对象。
Series¶
Series是一组带索引的数组。包含一组数据(各种NumPy数据类型)和与之相关的一组数据标签(即索引)。
可以用index和values分别规定索引和值。如果不规定索引,会自动创建 0 到 N-1 索引。
最简单的 Series¶
# 只包含一组数据的 Series
obj = Series([4, 7, -5, 3])
obj
obj.index
obj.values
指定索引¶
obj2 = Series([4, 7, -5, 3], index=['d', 'b', 'a', 'c'])
obj2
通过索引获取数据¶
# 取单行数据
obj2['d']
# 取多行数据
obj2[['c', 'a', 'd']]
按条件获取数据¶
obj2[obj2 > 0]
数组运算¶
对数组进行计算,只改变值,不会改变索引
obj2 * 2
np.exp(obj2)
改变索引¶
obj2
obj2.index = ['Bob', 'Steve', 'Jeff', 'Ryan']
obj2
作为Dictionary¶
可以将 Series 看作一个定长有序字典。
'b' in obj2
# 通过 Dictionary 直接创建 Series
sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 16000, 'Utah': 5000}
obj3 = Series(sdata)
obj3
# 重建索引,并按索引筛选数据
# 注意,索引 `California` 对应的值为 NaN
states = ['California', 'Ohio', 'Oregon', 'Texas']
obj4 = Series(sdata, index=states)
obj4
数据检查¶
pd.isnull(obj4) # 等价于 obj4.isnull()
pd.notnull(obj4) # 等价于 obj4.notnull()
自动对齐索引¶
Series很重要的功能就是按照索引自动对齐。
obj3
obj4
obj3 + obj4
命名¶
Series 自身和索引都可以命名。这个设计对于 Pandas 其他模块很重要。
obj4.name = 'population'
obj4.index.name = 'state'
obj4
obj4.index
DataFrame¶
DataFrame 是一种表格型结构,含有一组有序的列,每一列可以是不同的数据类型。
DataFrame 可以看做是共用索引的、由Series组成的字典。其中,共用的索引是整个 DataFrame 的行索引,而各个 Series 的名字作为列索引。
DataFrame面向行和列的操作基本是平衡的。其实,DataFrame中的数据是以一个或者多个二维块存放的(不是列表、字典或者其他)。
创建 DataFrame¶
# 使用字典创建
data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'Nevada'],
'year': [2000, 2001, 2002, 2001, 2002],
'pop': [1.5, 1.7, 3.6, 2.4, 2.9]}
frame = DataFrame(data)
frame
# 更改列顺序
DataFrame(data, columns=['year', 'state', 'pop'])
# 与行索引的机制类似,找不到的列,会用 NaN 填充
frame2 = DataFrame(data, columns=['year', 'state', 'pop', 'debt'],
index=['one', 'two', 'three', 'four', 'five'])
frame2
得到列¶
# 列是关于 Series 的字典
frame2.columns
# 获取某一列
frame2['state']
# 也可以用属性的方式获取 DataFrame 中的某一列
frame2.year
# 获取多个列,还是 DataFrame, 此时为“切片”操作
frame2[['state','year']]
得到行¶
通过索引可以得到 DataFrame 的一行,行也是一个 Series.
frame2.ix['three']
按列赋值¶
# 整列赋相同值
frame2['debt'] = 16.5
frame2
# 用一个 array 或 narray 对列赋值
frame2['debt'] = np.arange(5.)
frame2
# 使用 Series,可以根据索引对列精确赋值
# 注意其中的空值
val = Series([-1.2, -1.5, -1.7], index=['two', 'four', 'five'])
frame2['debt'] = val
frame2
增加和删除列¶
# 对不存在的列赋值会增加列
frame2['eastern'] = frame2.state == 'Ohio'
frame2
# 使用 `del` 删除列
del frame2['eastern']
frame2
转置¶
frame2.T
更多创建方法¶
# 使用二维字典创建
pop = {'Nevada': {2001: 2.4, 2002: 2.9},
'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}}
frame3 = DataFrame(pop)
frame3
DataFrame(pop, index=[2001, 2002, 2003])
pdata = {'Ohio': frame3['Ohio'][:-1],
'Nevada': frame3['Nevada'][:2]}
DataFrame(pdata)
为索引和列命名¶
frame3.index.name = 'year'; frame3.columns.name = 'state'
frame3
DataFrame 的值¶
# 返回 ndarray
frame3.values
# 类型不同时,自动转换为最抽象的类型
frame2.values
索引对象¶
obj = Series(range(3), index=['a', 'b', 'c'])
index = obj.index
index
index[1:]
# Index 里面的数据不能更改
# index[1] = 'd'
index = pd.Index(np.arange(3))
obj2 = Series([1.5, -2.5, 0], index=index)
obj2.index is index
frame3
'Ohio' in frame3.columns
2003 in frame3.index
Index 不是数组,也不是 Series(看起来像)。Index 支持的一些方法如下:
- append 连接另一个 Index
- diff 计算差集
- intersection 计算交集
- union 计算并集
- isin 是否全包含
- delete 删除 i 初的元素,返回一个新的 Index
- drop 删除传入的值,返回一个新的 Index
- insert 插入到 i 处
- is_monotonic 是否各元素都大于等于前一个元素(顺序排列)
- is_unique 是否没有重复值
- unique 返回唯一值的数组
基本功能
%pylab inline
from pandas import Series, DataFrame
import pandas as pd
obj = Series([4.5, 7.2, -5.3, 3.6], index=['d', 'b', 'a', 'c'])
obj
# 如果传入的索引值在数据里不存在,则不会报错,而是添加缺失值的新行
obj2 = obj.reindex(['a', 'b', 'c', 'd', 'e'])
obj2
#如果不想用缺失值,可以用 fill_value 参数指定填充值
obj.reindex(['a', 'b', 'c', 'd', 'e'], fill_value=0)
obj3 = Series(['blue', 'purple', 'yellow'], index=[0, 2, 4])
# method 指定填充新值采用的差值方法
# ffill 或 pad 实现用前值填充(forward fill)
obj3.reindex(range(6), method='ffill')
# bfill 或 backfill 实现用后值填充
obj3.reindex(range(6), method='bfill')
# #DataFrame 的reindex可以修改行、列或者两个都改
frame = DataFrame(np.arange(9).reshape((3, 3)), index=['a', 'c', 'd'],
columns=['Ohio', 'Texas', 'California'])
frame
# 默认更改行索引
frame2 = frame.reindex(['a', 'b', 'c', 'd'])
frame2
# 使用 columns 更改列索引
states = ['Texas', 'Utah', 'California']
frame.reindex(columns=states)
# 同时更改行和列索引
# 注意插值只能用在行上
frame.reindex(index=['a', 'b', 'c', 'd'], method='ffill',
columns=states)
# 使用ix的标签索引功能,重新索引变得比较简洁
# 等价于 frame.reindex(index=['a', 'b', 'c', 'd'], columns=states)
frame.ix[['a', 'b', 'c', 'd'], states]
丢弃指定轴上的项¶
# drop() 方法:通过索引数组或列表丢弃多个项,返回一个新的对象
obj = Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e'])
new_obj = obj.drop('c')
new_obj
# 丢弃多项
obj.drop(['d', 'c'])
# 对于 DataFrame
data = DataFrame(np.arange(16).reshape((4, 4)),
index=['Ohio', 'Colorado', 'Utah', 'New York'],
columns=['one', 'two', 'three', 'four'])
data
# 丢弃行
data.drop(['Colorado', 'Ohio'])
# 丢弃一列
data.drop('two', axis=1)
# 丢弃多列
data.drop(['two', 'four'], axis=1)
索引、选取和过滤¶
obj = Series(np.arange(4.), index=['a', 'b', 'c', 'd'])
obj
# 通过索引选取, 以下两种方式等价
print(obj['b'])
print(obj[1])
# 连续切片
obj[2:4]
# 通过索引选取
obj[['b', 'a', 'd']]
# 通过序号选取
obj[[1, 3]]
# 条件筛选
obj[obj < 2]
# 针对索引的条件筛选
obj['b':'d']
# 筛选并赋值
obj['b':'d'] = 5
obj
# 对于 DataFrame
data = DataFrame(np.arange(16).reshape((4, 4)),
index=['Ohio', 'Colorado', 'Utah', 'New York'],
columns=['one', 'two', 'three', 'four'])
data
# 选取列
data['two']
# 选取多列
data[['three', 'one']]
#
data[:2]
data[data['three'] > 5]
data < 5
data[data < 5] = 0
data
# 选择一行中的一部分
data.ix['Colorado', ['two', 'three']]
# 选择多行中的一部分
data.ix[['Colorado', 'Utah'], [3, 0, 1]]
# 一整行
data.ix[2]
data.ix[:'Utah', 'two']
data.ix[data.three > 5, :3]
算术运算和数据对齐¶
Pandas 在对 Series 和 DataFrame 进行算术运算时,能够根据索引自动对齐,其中索引不重合的部分值为NaN。
+,-,*,/ 对应的函数分别为 add()
, sub()
, mul()
, div()
。
s1 = Series([7.3, -2.5, 3.4, 1.5], index=['a', 'c', 'd', 'e'])
s2 = Series([-2.1, 3.6, -1.5, 4, 3.1], index=['a', 'c', 'e', 'f', 'g'])
s1
s2
s1 + s2
df1 = DataFrame(np.arange(9.).reshape((3, 3)), columns=list('bcd'),
index=['Ohio', 'Texas', 'Colorado'])
df2 = DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'),
index=['Utah', 'Ohio', 'Texas', 'Oregon'])
df1
df2
df1 + df2
在算术方法中填充值¶
df1 = DataFrame(np.arange(12.).reshape((3, 4)), columns=list('abcd'))
df2 = DataFrame(np.arange(20.).reshape((4, 5)), columns=list('abcde'))
df1
df2
df1 + df2
df1.add(df2, fill_value=0)
df1.reindex(columns=df2.columns, fill_value=0)
DataFrame 和 Series 之间的运算¶
arr = np.arange(12.).reshape((3, 4))
arr
arr[0]
arr - arr[0]
frame = DataFrame(np.arange(12.).reshape((4, 3)), columns=list('bde'),
index=['Utah', 'Ohio', 'Texas', 'Oregon'])
series = frame.ix[0]
frame
series
# 将Series的索引匹配到DataFrame的列,然后进行计算,再沿着行一直向下广播
frame - series
series2 = Series(range(3), index=['b', 'e', 'f'])
series2
# 非共有的列,自动填充
frame + series2
series3 = frame['d']
frame
series3
# 使用算术运算方法,可以指定轴,从而实现匹配行且在列上广播
frame.sub(series3, axis=0)
函数应用和映射¶
frame = DataFrame(np.random.randn(4, 3), columns=list('bde'),
index=['Utah', 'Ohio', 'Texas', 'Oregon'])
frame
# 求绝对值函数
np.abs(frame)
#另一种常见的做法是:将一个函数应用到行或者列上,用apply方法
# 默认应用到行,广播到列
f = lambda x: x.max() - x.min()
frame.apply(f)
# 通过指定 axis,可以应用到列,广播到行
frame.apply(f, axis=1)
# 传递的函数还可以返回 Series
def f(x):
return Series([x.min(), x.max()], index=['min', 'max'])
frame.apply(f)
# applymap: 应用到元素(而不是行和列)
format = lambda x: '%.2f' % x
frame.applymap(format)
# 因为 frame 调用了 Series 的 map()函数
frame['e'].map(format)
#用sort_index函数对行、列的索引进行排序
obj = Series(range(4), index=['d', 'a', 'b', 'c'])
obj.sort_index()
frame = DataFrame(np.arange(8).reshape((2, 4)), index=['three', 'one'],
columns=['d', 'a', 'b', 'c'])
frame.sort_index()
frame.sort_index(axis=1)
frame.sort_index(axis=1, ascending=False)
# 对值排序,使用 sort_values()
obj = Series([4, 7, -3, 2])
obj.sort_values()
obj = Series([4, np.nan, 7, np.nan, -3, 2])
obj.sort_values()
frame = DataFrame({'b': [4, 7, -3, 2], 'a': [0, 1, 0, 1]})
frame
frame.sort_values(by='b')
frame.sort_values(by=['a', 'b'])
#rank函数返回从小到大排序的下标,对于平级的数,rank是通过“为各组分配一个平均排名”的方式破坏平级关系
#下标从1开始
obj = Series([7, -5, 7, 4, 2, 0, 4])
obj.rank()
obj.rank(method='first')
obj.rank(ascending=False, method='max')
frame = DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1],
'c': [-2, 5, 8, -2.5]})
frame
frame.rank(axis=1)
带有重复值的轴索引¶
obj = Series(range(5), index=['a', 'a', 'b', 'b', 'c'])
obj
#用is_unique看是否唯一
obj.index.is_unique
# #对于重复值的索引,选取的话返回一个Series
obj['a']
# 唯一的索引返回一个标量
obj['c']
df = DataFrame(np.random.randn(4, 3), index=['a', 'a', 'b', 'b'])
df
df.ix['b']
汇总和描述性统计
%pylab inline
from pandas import Series, DataFrame
import pandas as pd
《利用Python进行数据分析》 作者: Wes McKinney 译者: 唐学韬
汇总和描述性统计
Pandas 实现了一些简单简单的汇总和描述性统计功能,更复杂的统计计算可以使用 Scipy 的 stats
模块。
基本统计¶
包括:
- count: 计数
- describe: 各列的汇总统计
- min, max: 最小值和最大值
- argmin, argmax: 最小值和最大值的索引位置
- idxmin, idxmax: 最小值和最大值 的索引值
- quantile: 样本的分位数(0--1)
- sum: 求和
- mean: 平均数
- median: 中位数(0.5分位数)
- mad: 平均绝对离差
- var: 方差
- std: 标准差
- skew: 偏度(三阶矩)
- kurt: 峰度(四阶矩)
- cumsum: 累计和
- cummin, cummax: 累计最小、最大值
- cumprod: 累计积
- diff: 一阶差分(用于时间序列)
- pct_change: 百分数变化
df = DataFrame([[1.4, np.nan], [7.1, -4.5],
[np.nan, np.nan], [0.75, -1.3]],
index=['a', 'b', 'c', 'd'],
columns=['one', 'two'])
df
# 求和
df.sum()
# 按行求和
df.sum(axis=1)
# 不忽略 NA
df.mean(axis=1, skipna=False)
# 返回索引
df.idxmax()
# 累加型
df.cumsum()
# 描述
df.describe()
# 非数值型的描述
obj = Series(['a', 'a', 'b', 'c'] * 4)
obj.describe()
相关系数(Correlation)与协方差(covariance)¶
from pandas_datareader import data, wb
all_data = {}
for ticker in ['AAPL', 'IBM', 'MSFT', 'GOOG']:
all_data[ticker] = data.get_data_yahoo(ticker)
price = DataFrame({tic: data['Adj Close']
for tic, data in all_data.items()})
volume = DataFrame({tic: data['Volume']
for tic, data in all_data.items()})
price
returns = price.pct_change()
returns.tail()
# 相关系数
print(returns.MSFT.corr(returns.IBM))
# 协方差
print(returns.MSFT.cov(returns.IBM))
# 相关系数矩阵
returns.corr()
# 协方差矩阵
returns.cov()
# 一组相关系数
returns.corrwith(returns.IBM)
# 一组相关系数( 回报百分比与成交量之间)
returns.corrwith(volume)
唯一值,值计数和成员资格¶
obj = Series(['c', 'a', 'd', 'a', 'a', 'b', 'b', 'c', 'c'])
uniques = obj.unique()
uniques
obj.value_counts()
pd.value_counts(obj.values, sort=False)
mask = obj.isin(['b', 'c'])
mask
# 通过 `isin()`的结果进行筛选
obj[mask]
data = DataFrame({'Qu1': [1, 3, 4, 3, 4],
'Qu2': [2, 3, 1, 2, 3],
'Qu3': [1, 5, 2, 4, 4]})
data
result = data.apply(pd.value_counts).fillna(0)
result
处理缺失数据
%pylab inline
from pandas import Series, DataFrame
import pandas as pd
《利用Python进行数据分析》 作者: Wes McKinney 译者: 唐学韬
处理缺失数据
数据不完整在数据分析的过程中很常见。
pandas使用浮点值NaN
表示浮点和非浮点数组里的缺失数据。
pandas使用isnull()
和notnull()
函数来判断缺失情况。
对于缺失数据一般处理方法为:
- 滤掉, 使用
dropna()
- 填充,使用
fillna
,比如 ffill(指定值)或 bfill(插值
string_data = Series(['aardvark', 'artichoke', np.nan, 'avocado'])
string_data
string_data.isnull()
# None 也被认为是缺失
string_data[0] = None
string_data.isnull()
滤除缺失数据¶
from numpy import nan as NA
data = Series([1, NA, 3.5, NA, 7])
data
# 使用 dropna()方法滤除
data.dropna()
# 等价方法
data[data.notnull()]
# 对于 DataFrame, 会 drop 掉所有有 NA 的行
data = DataFrame([[1., 6.5, 3.], [1., NA, NA],
[NA, NA, NA], [NA, 6.5, 3.]])
cleaned = data.dropna()
data
cleaned
# 只丢弃全为空的行
data.dropna(how='all')
data[4] = NA
data
# 丢弃列
data.dropna(axis=1, how='all')
df = DataFrame(np.random.randn(7, 3))
df.ix[:4, 1] = NA; df.ix[:2, 2] = NA
df
# thresh :选取至少包含多少个非空值的行
df.dropna(thresh=2)
填充缺失数据¶
# 用指定值填充
df.fillna(0)
# 每列用不同的值填充
df.fillna({1: 0.5, 3: -1})
# inplace: 不返回新对象,就地修改
_ = df.fillna(0, inplace=True)
df
df = DataFrame(np.random.randn(6, 3))
df.ix[2:, 1] = NA; df.ix[4:, 2] = NA
df
# 用前值填充
df.fillna(method='ffill')
# 用前值,最多填充2次
df.fillna(method='ffill', limit=2)
# 用平均值填充
data = Series([1., NA, 3.5, NA, 7])
data.fillna(data.mean())
fillna()参数汇总:
- value
- method, 插值方法,默认为 ffill
- axis,
- inplace
- limit
层次化索引
%pylab inline
from pandas import Series, DataFrame
import pandas as pd
《利用Python进行数据分析》 作者: Wes McKinney 译者: 唐学韬
层次化索引
层次化索引(hierarchical index)是pandas的重要功能,
通过在一个轴上拥有两个以上的索引级别, 使得 Pandas 可以用低维度形式处理高维度。
层次化索引在数据重塑和基于分组操作(如透视表生成)中扮演者重要的角色。
根据索引选择数据子集¶
data = Series(np.random.randn(10),
index=[['a', 'a', 'a', 'b', 'b', 'b', 'c', 'c', 'd', 'd'],
[1, 2, 3, 1, 2, 3, 1, 2, 2, 3]])
data
data.index
# 以外层索引的方式选择
data['b']
data['b':'c']
data.ix[['b', 'd']]
# 以内层索引的方式选择
data[:, 2]
stack()
和 unstack()
¶
# 层次化索引数据可以转换为一个DataFrame
data.unstack()
# 反向
data.unstack().stack()
DataFrame 的层次化索引¶
# 对于DataFrame,横轴和竖轴都可以有层次化索引
frame = DataFrame(np.arange(12).reshape((4, 3)),
index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
columns=[['Ohio', 'Ohio', 'Colorado'],
['Green', 'Red', 'Green']])
frame
# 每层索引都可以命名
frame.index.names = ['key1', 'key2']
frame.columns.names = ['state', 'color']
frame
frame['Ohio']
# 可以单独创建MultiIndex然后复用
columns = pd.MultiIndex.from_arrays([['Ohio','Ohio','Colorado'],['Green','Red','Green']],names = ['state','color'])
frame1 = DataFrame(np.arange(12).reshape((4,3)),columns = columns)
frame1
重排分级顺序¶
frame
frame.swaplevel('key1', 'key2')
frame.sortlevel(1)
frame.swaplevel(0, 1).sortlevel(0)
根据级别汇总统计¶
frame.sum(level='key2')
frame.sum(level='color', axis=1)
使用 DataFrame 列¶
frame = 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]})
frame
# set_index(): 将列转化为索引
frame2 = frame.set_index(['c', 'd'])
frame2
# 转化为索引,同时保留列
frame.set_index(['c', 'd'], drop=False)
# reset_index(): 将索引转化为列
frame2.reset_index()
其他
%pylab inline
from pandas import Series, DataFrame
import pandas as pd
《利用Python进行数据分析》 作者: Wes McKinney 译者: 唐学韬
其他关于 Pandas的话题
整数索引¶
ser = Series(np.arange(3.))
ser
ser.iloc[-1]
ser2 = Series(np.arange(3.), index=['a', 'b', 'c'])
ser2[-1]
#ix函数总是面向标签的
ser.ix[:1]
ser3 = Series(range(3), index=[-5, 1, 3])
ser3.iloc[2]
#如果需要可靠的、不考虑索引类型的、基于位置的索引,可以使用Series的iget_value方法,Dataframe的irow 和 icol方法
frame = DataFrame(np.arange(6).reshape((3, 2)), index=[2, 0, 1])
frame.iloc[0]
面板数据¶
可以看做是三维的 DataFrame, 但是通常用多层索引的 DataFrame,而不是使用 Panel。
#可以利用DataFrame对象组成的字典或者一个三维ndarray来创建Panel对象
from pandas_datareader import data, wb
pdata = pd.Panel(dict((stk, data.get_data_yahoo(stk))
for stk in ['AAPL', 'GOOG', 'MSFT', 'DELL']))
pdata
pdata = pdata.swapaxes('items', 'minor')
pdata['Adj Close']
pdata.ix[:, '6/1/2012', :]
pdata.ix['Adj Close', '5/22/2012':, :]
stacked = pdata.ix[:, '5/30/2012':, :].to_frame()
stacked
stacked.to_panel()