Pandas
文档:
中文文档:https://www.pypandas.cn/docs/
Pandas是一个高性能的数据操作和分析工具。它在Numpy的基础上,提供了一种高效的DataFrame数据结构,使得在Python中进行数据清洗和分析非常快捷。Pandas采用了很多Numpy的代码风格,但最大的不同在于pandas主要用来处理表格型或异质型数据,而Numpy则相反,它更适合处理同质并且是数值类型的数据。事实上大多数时候,我们使用Pandas多于Numpy。
Pandas最初由AQR Capital Management于2008年4月开发,并于2009年底开源出来,目前由专注于Python数据包开发的PyData开发团队继续开发和维护,属于PyData项目的一部分。Pandas最初被作为金融数据分析工具而开发出来,因此,pandas为时间序列分析提供了很好的支持。 Pandas的名称来自于面板数据(panel data)和python数据分析(data analysis)。面板数据是经济学中关于多维数据集的一个术语,在Pandas中也提供了panel的数据类型。
Pandas的主要特点:
- 快速高效的DataFrame对象,具有默认和自定义的索引。
- 将数据从不同文件格式加载到内存中的数据对象的工具。
- 丢失数据的数据对齐和综合处理。
- 重组和摆动日期集。
- 基于标签的切片,索引和大数据集的子集。
- 可以删除或插入来自数据结构的列。
- 按数据分组进行聚合和转换。
- 高性能合并和数据加入。
- 时间序列功能。
通常我们都使用Anaconda发行版安装Pandas,如果你非要自己安装,可以使用:
pip install pandas
conda install pandas
python3 -m pip install --upgrade pandas
对于Linux,比如Ubuntu,可以使用下面的方法安装,但可能出现各种依赖缺失或者安装错误:
sudo apt-get install python-numpy python-scipy python-matplotlib ipython python-pandas python-sympy python-nose
安装完Pandas后,我们就可以在notebook中导入它了,通常我们会使用下面的国际惯例进行导入:
import pandas as pd
有时候,我们也会将它包含的两个重要数据结构也单独导入:
from pandas import Series, DataFrame
可以如下查看当前Pandas的版本信息:
pd.__version__
Series
Pandas的核心是三大数据结构:Series、DataFrame和Index。绝大多数操作都是围绕这三种结构进行的。
pd.Series(data, index=index)
其中,index 是一个可选参数,data 参数支持多种数据类型。
Series是一个一维的数组对象,它包含一个值序列和一个对应的索引序列。 Numpy的一维数组通过隐式定义的整数索引获取元素值,而Series用一种显式定义的索引与元素关联。显式索引让Series对象拥有更强的能力,索引也不再仅仅是整数,还可以是别的类型,比如字符串,索引也不需要连续,也可以重复,自由度非常高。
最基本的生成方式是使用Series构造器:
In [1]: import pandas as pd
In [4]: s = pd.Series([7,-3,4,-2])
In [5]: s
Out[5]:
0 7
1 -3
2 4
3 -2
dtype: int64
创建
import pandas as pd
import numpy as np
# 从ndarray创建一个系列
data = np.array(['a','b','c','d'])
s = pd.Series(data)
s = pd.Series(data,index=[100,101,102,103])
# 从字典创建一个系列
data = {'a' : 0., 'b' : 1., 'c' : 2.}
s = pd.Series(data)
# 从标量创建一个系列
s = pd.Series(5, index=[0, 1, 2, 3])
打印的时候,自动对齐了,看起来比较美观。左边是索引,右边是实际对应的值。默认的索引是0到N-1(N是数据的长度)。可以通过values和index属性分别获取Series对象的值和索引:
In [5]: s.dtype
Out[5]: dtype('int64')
In [6]: s.values
Out[6]: array([ 7, -3, 4, -2], dtype=int64)
In [7]: s.index
Out[7]: RangeIndex(start=0, stop=4, step=1)
可以在创建Series对象的时候指定索引:
In [8]: s2 = pd.Series([7,-3,4,-2], index=['d','b','a','c'])
In [9]: s2
Out[9]:
d 7
b -3
a 4
c -2
dtype: int64
In [10]: s2.index
Out[10]: Index(['d', 'b', 'a', 'c'], dtype='object')
In [4]: pd.Series(5, index=list('abcde'))
Out[4]:
a 5
b 5
c 5
d 5
e 5
dtype: int64
In [5]: pd.Series({2:'a',1:'b',3:'c'}, index=[3,2]) # 通过index筛选结果
Out[5]:
3 c
2 a
dtype: object
也可以在后期,直接修改index:
In [33]: s
Out[33]:
0 7
1 -3
2 4
3 -2
dtype: int64
In [34]: s.index = ['a','b','c','d']
In [35]: s
Out[35]:
a 7
b -3
c 4
d -2
dtype: int64
类似Python的列表和Numpy的数组,Series也可以通过索引获取对应的值:
In [11]: s2['a']
Out[11]: 4
In [12]: s2[['c','a','d']]
Out[12]:
c -2
a 4
d 7
dtype: int64
也可以对Seires执行一些类似Numpy的通用函数操作:
In [13]: s2[s2>0]
Out[13]:
d 7
a 4
dtype: int64
In [14]: s2*2
Out[14]:
d 14
b -6
a 8
c -4
dtype: int64
In [15]: import numpy as np
In [16]: np.exp(s2)
Out[16]:
d 1096.633158
b 0.049787
a 54.598150
c 0.135335
dtype: float64
因为索引可以是字符串,所以从某个角度看,Series又比较类似Python的有序字典,所以可以使用in操作:
In [17]: 'b' in s2
Out[17]: True
In [18]: 'e'in s2
Out[18]: False
自然,我们也会想到使用Python的字典来创建Series:
In [19]: dic = {'beijing':35000,'shanghai':71000,'guangzhou':16000,'shenzhen':5000}
In [20]: s3=pd.Series(dic)
In [21]: s3
Out[21]:
beijing 35000
shanghai 71000
guangzhou 16000
shenzhen 5000
dtype: int64
In [14]: s3.keys() # 自然,具有类似字典的方法
Out[14]: Index(['beijing', 'shanghai', 'guangzhou', 'shenzhen'], dtype='object')
In [15]: s3.items()
Out[15]: <zip at 0x1a5c2d88c88>
In [16]: list(s3.items())
Out[16]:
[('beijing', 35000),
('shanghai', 71000),
('guangzhou', 16000),
('shenzhen', 5000)]
In [18]: s3['changsha'] = 20300
我们看下面的例子:
In [22]: city = ['nanjing', 'shanghai','guangzhou','beijing']
In [23]: s4=pd.Series(dic, index=city)
In [24]: s4
Out[24]:
nanjing NaN
shanghai 71000.0
guangzhou 16000.0
beijing 35000.0
dtype: float64
city列表中,多了‘nanjing’,但少了‘shenzhen’。Pandas会依据city中的关键字去dic中查找对应的值,因为dic中没有‘nanjing’,这个值缺失,所以以专门的标记值NaN表示。因为city中没有‘shenzhen’,所以在s4中也不会存在‘shenzhen’这个条目。可以看出,索引很关键,在这里起到了决定性的作用。
在Pandas中,可以使用isnull和notnull函数来检查缺失的数据:
In [25]: pd.isnull(s4)
Out[25]:
nanjing True
shanghai False
guangzhou False
beijing False
dtype: bool
In [26]: pd.notnull(s4)
Out[26]:
nanjing False
shanghai True
guangzhou True
beijing True
dtype: bool
In [27]: s4.isnull()
Out[27]:
nanjing True
shanghai False
guangzhou False
beijing False
dtype: bool
可以为Series对象和其索引设置name属性,这有助于标记识别:
In [29]: s4.name = 'people'
In [30]: s4.index.name= 'city'
In [31]: s4
Out[31]:
city
nanjing NaN
shanghai 71000.0
guangzhou 16000.0
beijing 35000.0
Name: people, dtype: float64
In [32]: s4.index
Out[32]: Index(['nanjing', 'shanghai', 'guangzhou', 'beijing'], dtype='object', name='city')
DataFrame
DataFrame是Pandas的核心数据结构,表示的是二维的矩阵数据表,类似关系型数据库的结构,每一列可以是不同的值类型,比如数值、字符串、布尔值等等。DataFrame既有行索引,也有列索引,它可以被看做为一个共享相同索引的Series的字典。
创建DataFrame对象的方法有很多,最常用的是利用包含等长度列表或Numpy数组的字典来生成。可以查看DataFrame对象的columns和index属性。
In [37]: data = {'state':['beijing','beijing','beijing','shanghai','shanghai','shanghai'],
...: 'year':[2000,2001,2002,2001,2002,2003],
...: 'pop':[1.5, 1.7,3.6,2.4,2.9,3.2
...: ]}
In [38]: f = pd.DataFrame(data)
In [39]: f
Out[39]:
state year pop
0 beijing 2000 1.5
1 beijing 2001 1.7
2 beijing 2002 3.6
3 shanghai 2001 2.4
4 shanghai 2002 2.9
5 shanghai 2003 3.2
In [61]: f.columns
Out[61]: Index(['state', 'year', 'pop'], dtype='object')
In [62]: f.index
Out[62]: RangeIndex(start=0, stop=6, step=1)
In [10]: f.dtypes
Out[10]:
state object
year int64
pop float64
dtype: object
In [11]: f.values # 按行查看
Out[11]:
array([['beijing', 2000, 1.5],
['beijing', 2001, 1.7],
['beijing', 2002, 3.6],
['shanghai', 2001, 2.4],
['shanghai', 2002, 2.9],
['shanghai', 2003, 3.2]], dtype=object)
上面自动生成了0-5的索引。
可以使用head方法查看DataFrame对象的前5行,用tail方法查看后5行。或者head(3),tail(3)指定查看行数:
In [40]: f.head()
Out[40]:
state year pop
0 beijing 2000 1.5
1 beijing 2001 1.7
2 beijing 2002 3.6
3 shanghai 2001 2.4
4 shanghai 2002 2.9
In [41]: f.tail()
Out[41]:
state year pop
1 beijing 2001 1.7
2 beijing 2002 3.6
3 shanghai 2001 2.4
4 shanghai 2002 2.9
5 shanghai 2003 3.2
DataFrame对象中的state/year/pop,其实就是列索引,可以在创建的时候使用参数名columns指定它们的先后顺序:
In [44]: pd.DataFrame(data, columns=['year','state','pop'])
Out[44]:
year state pop
0 2000 beijing 1.5
1 2001 beijing 1.7
2 2002 beijing 3.6
3 2001 shanghai 2.4
4 2002 shanghai 2.9
5 2003 shanghai 3.2
当然,也可以使用参数名index指定行索引:
In [45]: f2 = pd.DataFrame(data, columns=['year','state','pop'],index=['a','b','c','d','e','f'])
In [47]: f2
Out[47]:
year state pop
a 2000 beijing 1.5
b 2001 beijing 1.7
c 2002 beijing 3.6
d 2001 shanghai 2.4
e 2002 shanghai 2.9
f 2003 shanghai 3.2
可以使用columns列索引来检索一列:
In [49]: f2['year']
Out[49]:
a 2000
b 2001
c 2002
d 2001
e 2002
f 2003
Name: year, dtype: int64
In [52]: f2.state # 属性的形式来检索。这种方法bug多,比如属性名不是纯字符串,或者与其它方法同名
Out[52]:
a beijing
b beijing
c beijing
d shanghai
e shanghai
f shanghai
Name: state, dtype: object
但是检索一行却不能通过f2['a']这种方式,而是需要通过loc方法进行选取:
In [53]: f2.loc['a']
Out[53]:
year 2000
state beijing
pop 1.5
Name: a, dtype: object
当然,可以给DataFrame对象追加列:
In [54]: f2['debt'] = 12
In [55]: f2
Out[55]:
year state pop debt
a 2000 beijing 1.5 12
b 2001 beijing 1.7 12
c 2002 beijing 3.6 12
d 2001 shanghai 2.4 12
e 2002 shanghai 2.9 12
f 2003 shanghai 3.2 12
In [56]: f2['debt'] = np.arange(1,7)
In [57]: f2
Out[57]:
year state pop debt
a 2000 beijing 1.5 1
b 2001 beijing 1.7 2
c 2002 beijing 3.6 3
d 2001 shanghai 2.4 4
e 2002 shanghai 2.9 5
f 2003 shanghai 3.2 6
In [58]: val = pd.Series([1,2,3],index = ['c','d','f'])
In [59]: f2['debt'] = val
In [60]: f2 # 缺失值以NaN填补
Out[60]:
year state pop debt
a 2000 beijing 1.5 NaN
b 2001 beijing 1.7 NaN
c 2002 beijing 3.6 1.0
d 2001 shanghai 2.4 2.0
e 2002 shanghai 2.9 NaN
f 2003 shanghai 3.2 3.0
那么如何给DataFrame追加行呢?
>>> data = {'state':['beijing','beijing','beijing','shanghai','shanghai','shanghai'],
...: 'year':[2000,2001,2002,2001,2002,2003],
...: 'pop':[1.5, 1.7,3.6,2.4,2.9,3.2
...: ]}
>>> df = pd.DataFrame(data,index=list('abcdef'))
>>> df
state year pop
a beijing 2000 1.5
b beijing 2001 1.7
c beijing 2002 3.6
d shanghai 2001 2.4
e shanghai 2002 2.9
f shanghai 2003 3.2
>>> df1 = df.loc['a']
>>> df1
state beijing
year 2000
pop 1.5
Name: a, dtype: object
>>> df.append(df1)
state year pop
a beijing 2000 1.5
b beijing 2001 1.7
c beijing 2002 3.6
d shanghai 2001 2.4
e shanghai 2002 2.9
f shanghai 2003 3.2
a beijing 2000 1.5
可以使用del方法删除指定的列:
In [63]: f2['new'] = f2.state=='beijing'
In [64]: f2
Out[64]:
year state pop debt new
a 2000 beijing 1.5 NaN True
b 2001 beijing 1.7 NaN True
c 2002 beijing 3.6 1.0 True
d 2001 shanghai 2.4 2.0 False
e 2002 shanghai 2.9 NaN False
f 2003 shanghai 3.2 3.0 False
In [65]: del f2.new # 要注意的是我们有时候不能这么调用f2的某个列,并执行某个操作。这是个坑。
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
<ipython-input-65-03e4ec812cdb> in <module>()
----> 1 del f2.new
AttributeError: new
In [66]: del f2['new']
In [67]: f2.columns
Out[67]: Index(['year', 'state', 'pop', 'debt'], dtype='object')
需要注意的是:从DataFrame中选取的列是数据的视图,而不是拷贝。因此,对选取列的修改会反映到DataFrame上。如果需要复制,应当使用copy方法。
可以使用类似Numpy的T属性,将DataFrame进行转置:
In [68]: f2.T
Out[68]:
a b c d e f
year 2000 2001 2002 2001 2002 2003
state beijing beijing beijing shanghai shanghai shanghai
pop 1.5 1.7 3.6 2.4 2.9 3.2
debt NaN NaN 1 2 NaN 3
DataFrame对象同样具有列名、索引名,也可以查看values:
In [70]: f2.index.name = 'order';f2.columns.name='key'
In [71]: f2
Out[71]:
key year state pop debt
order
a 2000 beijing 1.5 NaN
b 2001 beijing 1.7 NaN
c 2002 beijing 3.6 1.0
d 2001 shanghai 2.4 2.0
e 2002 shanghai 2.9 NaN
f 2003 shanghai 3.2 3.0
In [72]: f2.values
Out[72]:
array([[2000, 'beijing', 1.5, nan],
[2001, 'beijing', 1.7, nan],
[2002, 'beijing', 3.6, 1.0],
[2001, 'shanghai', 2.4, 2.0],
[2002, 'shanghai', 2.9, nan],
[2003, 'shanghai', 3.2, 3.0]], dtype=object)
最后,DataFrame有一个Series所不具备的方法,那就是info!通过这个方法,可以看到DataFrame的一些整体信息情况:
In [73]: f.info()
Out[73]:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 6 entries, 0 to 5
Data columns (total 3 columns):
state 6 non-null object
year 6 non-null int64
pop 6 non-null float64
dtypes: float64(1), int64(1), object(1)
memory usage: 224.0+ bytes
Index
Pandas中的索引对象Index用于存储轴标签和其它元数据。索引对象是不可变的,用户无法修改它。
In [73]: obj = pd.Series(range(3),index = ['a','b','c'])
In [74]: index = obj.index
In [75]: index
Out[75]: Index(['a', 'b', 'c'], dtype='object')
In [76]: index[1:]
Out[76]: Index(['b', 'c'], dtype='object')
In [77]: index[1] = 'f' # TypeError
In [8]: index.size
Out[8]: 3
In [9]: index.shape
Out[9]: (3,)
In [10]: index.ndim
Out[10]: 1
In [11]: index.dtype
Out[11]: dtype('O')
索引对象的不可变特性,使得在多种数据结构中分享索引对象更安全:
In [78]: labels = pd.Index(np.arange(3))
In [79]: labels
Out[79]: Int64Index([0, 1, 2], dtype='int64')
In [80]: obj2 = pd.Series([2,3.5,0], index=labels)
In [81]: obj2
Out[81]:
0 2.0
1 3.5
2 0.0
dtype: float64
In [82]: obj2.index is labels
Out[82]: True
索引对象,本质上也是一个容器对象,所以可以使用Python的in操作:
In [84]: f2
Out[84]:
key year state pop debt
order
a 2000 beijing 1.5 NaN
b 2001 beijing 1.7 NaN
c 2002 beijing 3.6 1.0
d 2001 shanghai 2.4 2.0
e 2002 shanghai 2.9 NaN
f 2003 shanghai 3.2 3.0
In [86]: 'c' in f2.index
Out[86]: True
In [88]: 'pop' in f2.columns
Out[88]: True
而且最关键的是,pandas的索引对象可以包含重复的标签:
In [89]: dup_lables = pd.Index(['foo','foo','bar','bar'])
In [90]: dup_lables
Out[90]: Index(['foo', 'foo', 'bar', 'bar'], dtype='object')
那么思考一下,DataFrame对象可不可以有重复的columns或者index呢?
可以的!但是请尽量不要这么做!:
In [91]: f2.index = ['a']*6
In [92]: f2
Out[92]:
key year state pop debt
a 2000 beijing 1.5 NaN
a 2001 beijing 1.7 NaN
a 2002 beijing 3.6 1.0
a 2001 shanghai 2.4 2.0
a 2002 shanghai 2.9 NaN
a 2003 shanghai 3.2 3.0
In [93]: f2.loc['a']
Out[93]:
key year state pop debt
a 2000 beijing 1.5 NaN
a 2001 beijing 1.7 NaN
a 2002 beijing 3.6 1.0
a 2001 shanghai 2.4 2.0
a 2002 shanghai 2.9 NaN
a 2003 shanghai 3.2 3.0
In [94]: f2.columns = ['year']*4
In [95]: f2
Out[95]:
year year year year
a 2000 beijing 1.5 NaN
a 2001 beijing 1.7 NaN
a 2002 beijing 3.6 1.0
a 2001 shanghai 2.4 2.0
a 2002 shanghai 2.9 NaN
a 2003 shanghai 3.2 3.0
In [96]: f2.index.is_unique # 可以使用这个属性来判断是否存在重复的索引
Out[96]: False
index对象也可以进行集合的交、并、差和异或运算,类似Python的标准set数据结构。
In[35]: indA = pd.Index([1, 3, 5, 7, 9])
indB = pd.Index([2, 3, 5, 7, 11])
In[36]: indA & indB # 交集
Out[36]: Int64Index([3, 5, 7], dtype='int64')
In[37]: indA | indB # 并集
Out[37]: Int64Index([1, 2, 3, 5, 7, 9, 11], dtype='int64')
In[38]: indA ^ indB # 异或
Out[38]: Int64Index([1, 2, 9, 11], dtype='int64')
数据取值与选择
从简单的一维 Series 对象开始,然后再用比较复杂的二维 DataFrame 对象进行演示。
1、Series数据选择方法
Series 对象提供了键值对的映射:
In[1]: import pandas as pd
data = pd.Series([0.25, 0.5, 0.75, 1.0],
index=['a', 'b', 'c', 'd'])
data
Out[1]: a 0.25
b 0.50
c 0.75
d 1.00
dtype: float64
In[2]: data['b']
Out[2]: 0.5
还可以用 Python 字典的表达式和方法来检测键 / 索引和值:
In[3]: 'a' in data
Out[3]: True
In[4]: data.keys()
Out[4]: Index(['a', 'b', 'c', 'd'], dtype='object')
In[5]: list(data.items())
Out[5]: [('a', 0.25), ('b', 0.5), ('c', 0.75), ('d', 1.0)]
Series 对象还可以用字典语法调整数据。就像你可以通过增加新的键扩展字典一样,你也可以通过增加新的索引值扩展 Series:
In[6]: data['e'] = 1.25
data
Out[6]: a 0.25
b 0.50
c 0.75
d 1.00
e 1.25
dtype: float64
Series 对象的可变性是一个非常方便的特性:Pandas 在底层已经为可能发生的内存布局和数据复制自动决策。
Series 不仅有着和字典一样的接口,而且还具备和 NumPy 数组一样的数组数据选择功能, 包括索引、掩码、花哨的索引等操作,具体示例如下所示:
In[7]: # 将显式索引作为切片
data['a':'c']
Out[7]: a 0.25
b 0.50
c 0.75
dtype: float64
In[8]: # 将隐式整数索引作为切片
data[0:2]
Out[8]: a 0.25
b 0.50
dtype: float64
In[9]: # 掩码
data[(data > 0.3) & (data < 0.8)]
Out[9]: b 0.50
c 0.75
dtype: float64
In[10]: # 花哨的索引
data[['a', 'e']]
Out[10]: a 0.25
e 1.25
dtype: float64
在以上示例中,切片是绝大部分混乱之源。需要注意的是,当使用显式索引(即 data['a':'c'])作切片时,结果包含最后一个索引;而当使用隐式索引(即 data[0:2]) 作切片时,结果不包含最后一个索引。
由于整数索引很容易造成混淆,所以 Pandas 提供了一些索引器(indexer)属性来作为取值的方法。它们不是 Series 对象的函数方法,而是暴露切片接口的属性。
第一种索引器是 loc 属性,表示取值和切片都是显式的:
In[14]: data.loc[1]
Out[14]: 'a'
In[15]: data.loc[1:3]
Out[15]: 1 a
3 b
dtype: object
第二种是 iloc 属性,表示取值和切片都是 Python 形式的隐式索引:
In[16]: data.iloc[1]
Out[16]: 'b'
In[17]: data.iloc[1:3]
Out[17]: 3 b
5 c
dtype: object
第三种取值属性是 ix,它是前两种索引器的混合形式,在 Series 对象中 ix 等价于标准的 [](Python 列表)取值方式。ix 索引器主要用于 DataFrame 对象,后面将会介绍。
Python 代码的设计原则之一是“显式优于隐式”。使用 loc 和 iloc 可以让代码更容易维护, 可读性更高。特别是在处理整数索引的对象时,我强烈推荐使用这两种索引器。它们既可以让代码阅读和理解起来更容易,也能避免因误用索引 / 切片而产生的小bug。
2、DataFrame数据选择方法
DataFrame 在有些方面像二维或结构化数组,在有些方面又像一个共享索引的若干 Series 对象构成的字典。这两种类比可以帮助我们更好地掌握这种数据结构的数据选择方法。
第一种类比是把 DataFrame 当作一个由若干 Series 对象构成的字典。让我们用之前的美国五州面积与人口数据来演示:
In[18]: area = pd.Series({'California': 423967, 'Texas': 695662, 'New York': 141297, 'Florida': 170312, 'Illinois': 149995})
pop = pd.Series({'California': 38332521, 'Texas': 26448193, 'New York': 19651127, 'Florida': 19552860, 'Illinois': 12882135})
data = pd.DataFrame({'area':area, 'pop':pop})
data
Out[18]: area pop
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
New York 141297 19651127
Texas 695662 26448193
两个 Series 分别构成 DataFrame 的一列,可以通过对列名进行字典形式(dictionary-style) 的取值获取数据:
In[19]: data['area']
Out[19]: California 423967
Florida 170312
Illinois 149995
New York 141297
Texas 695662
Name: area, dtype: int64
同样,也可以用属性形式(attribute-style)选择纯字符串列名的数据:
In[20]: data.area
Out[20]: California 423967
Florida 170312
Illinois 149995
New York 141297
Texas 695662
Name: area, dtype: int64
对同一个对象进行属性形式与字典形式的列数据,结果是相同的:
In[21]: data.area is data['area']
Out[21]: True
虽然属性形式的数据选择方法很方便,但是它并不是通用的。如果列名不是纯字符串,或者列名与 DataFrame 的方法同名,那么就不能用属性索引。例如,DataFrame 有一个 pop() 方法,如果用 data.pop 就不会获取 'pop' 列,而是显示为方法:
In[22]: data.pop is data['pop']
Out[22]: False
另外,还应该避免对用属性形式选择的列直接赋值(即可以用 data['pop'] = z,但不要用 data.pop = z)。
和前面介绍的 Series 对象一样,还可以用字典形式的语法调整对象,如果要增加一列可以这样做:
In[23]: data['density'] = data['pop'] / data['area']
data
Out[23]: area pop density
California 423967 38332521 90.413926
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
New York 141297 19651127 139.076746
Texas 695662 26448193 38.018740
这里演示了两个 Series 对象算术运算的简便语法
前面曾提到,可以把 DataFrame 看成是一个增强版的二维数组,用 values 属性按行查看数组数据:
In[24]: data.values
Out[24]: array([[ 4.23967000e+05, 3.83325210e+07, 9.04139261e+01],
[ 1.70312000e+05, 1.95528600e+07, 1.14806121e+02],
[ 1.49995000e+05, 1.28821350e+07, 8.58837628e+01],
[ 1.41297000e+05, 1.96511270e+07, 1.39076746e+02],
[ 6.95662000e+05, 2.64481930e+07, 3.80187404e+01]])
理解了这一点,就可以把许多数组操作方式用在 DataFrame 上。例如,可以对 DataFrame进行行列转置:
In[25]: data.T
Out[25]: California Florida Illinois New York Texas
area 4.239670e+05 1.703120e+05 1.499950e+05 1.412970e+05 6.956620e+05
pop 3.833252e+07 1.955286e+07 1.288214e+07 1.965113e+07 2.644819e+07
density 9.041393e+01 1.148061e+02 8.588376e+01 1.390767e+02 3.801874e+01
通过字典形式对列进行取值显然会限制我们把 DataFrame 作为 NumPy 数组可以获得的能力,尤其是当我们在 DataFrame 数组中使用单个行索引获取一行数据时:
In[26]: data.values[0]
Out[26]: array([ 4.23967000e+05, 3.83325210e+07, 9.04139261e+01])
而获取一列数据就需要向 DataFrame 传递单个列索引:
In[27]: data['area']
Out[27]: California 423967
Florida 170312
Illinois 149995
New York 141297
Texas 695662
Name: area, dtype: int64
因此,在进行数组形式的取值时,我们就需要用另一种方法——前面介绍过的 Pandas 索引器 loc、iloc 和 ix 了。通过 iloc 索引器,我们就可以像对待 NumPy 数组一样索引 Pandas 的底层数组(Python 的隐式索引),DataFrame 的行列标签会自动保留在结果中:
In[28]: data.iloc[:3, :2]
Out[28]: area pop
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
In[29]: data.loc[:'Illinois', :'pop']
Out[29]: area pop
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
使用 ix 索引器可以实现一种混合效果:
In[30]: data.ix[:3, :'pop']
Out[30]: area pop
California 423967 38332521
Florida 170312 19552860
Illinois 149995 12882135
需要注意的是,ix 索引器对于整数索引的处理和之前在 Series 对象中介绍的一样,都容易让人混淆。
任何用于处理 NumPy 形式数据的方法都可以用于这些索引器。例如,可以在 loc 索引器中结合使用掩码与花哨的索引方法:
In[31]: data.loc[data.density > 100, ['pop', 'density']]
Out[31]: pop density
Florida 19552860 114.806121
New York 19651127 139.076746
任何一种取值方法都可以用于调整数据,这一点和 NumPy 的常用方法是相同的:
In[32]: data.iloc[0, 2] = 90
data
Out[32]: area pop density
California 423967 38332521 90.000000
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
New York 141297 19651127 139.076746
Texas 695662 26448193 38.018740
如果你想熟练使用 Pandas 的数据操作方法,我建议你花点时间在一个简单的 DataFrame 上练习不同的取值方法,包括查看索引类型、切片、掩码和花哨的索引操作。
还有一些取值方法和前面介绍过的方法不太一样。它们虽然看着有点奇怪,但是在实践中还是很好用的。首先,如果对单个标签取值就选择列,而对多个标签用切片就选择行:
In[33]: data['Florida':'Illinois']
Out[33]: area pop density
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
切片也可以不用索引值,而直接用行数来实现:
In[34]: data[1:3]
Out[34]: area pop density
Florida 170312 19552860 114.806121
Illinois 149995 12882135 85.883763
与之类似,掩码操作也可以直接对每一行进行过滤,而不需要使用 loc 索引器:
In[35]: data[data.density > 100]
Out[35]: area pop density
Florida 170312 19552860 114.806121
New York 141297 19651127 139.076746
这两种操作方法其实与 NumPy 数组的语法类似,虽然它们与 Pandas 的操作习惯不太一致, 但是在实践中非常好用。
重建索引
reindex方法用于重新为Pandas对象设置新索引。这不是就地修改,而是会参照原有数据,调整顺序。
In [96]: obj=pd.Series([4.5,7.2,-5.3,3.6],index = ['d','b','a','c'])
In [97]: obj
Out[97]:
d 4.5
b 7.2
a -5.3
c 3.6
dtype: float64
reindex会按照新的索引进行排列,不存在的索引将引入缺失值:
In [99]: obj2 = obj.reindex(list('abcde'))
In [100]: obj2
Out[100]:
a -5.3
b 7.2
c 3.6
d 4.5
e NaN
dtype: float64
也可以为缺失值指定填充方式method参数,比如ffill表示向前填充,bfill表示向后填充:
In [101]: obj3 = pd.Series(['blue','purple','yellow'],index = [0,2,4])
In [102]: obj3
Out[102]:
0 blue
2 purple
4 yellow
dtype: object
In [103]: obj3.reindex(range(6),method='ffill')
Out[103]:
0 blue
1 blue
2 purple
3 purple
4 yellow
5 yellow
dtype: object
对于DataFrame这种二维对象,如果执行reindex方法时只提供一个列表参数,则默认是修改行索引。可以用关键字参数columns指定修改的是列索引:
In [104]: f = pd.DataFrame(np.arange(9).reshape((3,3)),index=list('acd'),columns=['beijing','shanghai','guangzhou'])
In [105]: f
Out[105]:
beijing shanghai guangzhou
a 0 1 2
c 3 4 5
d 6 7 8
In [106]: f2 = f.reindex(list('abcd'))
In [107]: f2
Out[107]:
beijing shanghai guangzhou
a 0.0 1.0 2.0
b NaN NaN NaN
c 3.0 4.0 5.0
d 6.0 7.0 8.0
In [112]: f3 = f.reindex(columns=['beijing','shanghai','xian','guangzhou'])
In [113]: f3
Out[113]:
beijing shanghai xian guangzhou
a 0 1 NaN 2
c 3 4 NaN 5
d 6 7 NaN 8
轴向上删除条目
通过drop方法,可以删除Series的一个元素,或者DataFrame的一行或一列。默认情况下,drop方法按行删除,且不会修改原数据,但指定axis=1则按列删除,指定inplace=True则修改原数据。
In [9]: s= pd.Series(np.arange(5),index=list('abcde'))
In [10]: s
Out[10]:
a 0
b 1
c 2
d 3
e 4
dtype: int32
In [11]: new_s = s.drop('c')
In [12]: new_s
Out[12]:
a 0
b 1
d 3
e 4
dtype: int32
In [13]: s
Out[13]:
a 0
b 1
c 2
d 3
e 4
dtype: int32
In [14]: df = pd.DataFrame(np.arange(16).reshape(4,4),columns=['one','two','three','four'])
In [15]: df
Out[15]:
one two three four
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
3 12 13 14 15
In [16]: df.drop(2)
Out[16]:
one two three four
0 0 1 2 3
1 4 5 6 7
3 12 13 14 15
In [17]: df.drop('two',axis = 1) # 指定删除列,而不是默认的行
Out[17]:
one three four
0 0 2 3
1 4 6 7
2 8 10 11
3 12 14 15
In [18]: df
Out[18]:
one two three four
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
3 12 13 14 15
In [21]: df.drop(2,inplace=True) #修改原数据
In [22]: df
Out[22]:
one two three four
0 0 1 2 3
1 4 5 6 7
3 12 13 14 15
索引和切片
Series的打印效果,让我们感觉它像个二维表格,实际上它还是一维的,其索引和numpy的一维数组比较类似,但还是有点区别的。
In [23]: se = pd.Series(np.linspace(1,4,5),index=list('abcde'))
In [24]: se
Out[24]:
a 1.00
b 1.75
c 2.50
d 3.25
e 4.00
dtype: float64
In [28]: se['b'] # 利用我们专门指定的索引进行检索
Out[28]: 1.75
In [29]: se[2] # 实际上默认还有一个从0开始的索引供我们使用
Out[29]: 2.5
In [30]: se[2:4]
Out[30]:
c 2.50
d 3.25
dtype: float64
In [31]: se[['b','a','d']] # 根据索引顺序,值进行相应的排序,而不是我们认为的按原来的顺序
Out[31]:
b 1.75
a 1.00
d 3.25
dtype: float64
In [32]: se[[1,3]] # 左闭右开 ,千万不要写成se[1,3]
Out[32]:
b 1.75
d 3.25
dtype: float64
In [33]: se[se>2]
Out[33]:
c 2.50
d 3.25
e 4.00
dtype: float64
In [35]: se['b':'c'] # 什么!居然是左闭右也闭!
Out[35]:
b 1.75
c 2.50
dtype: float64
In [36]: se['b':'c'] = 6 # 这样会修改原Series
In [37]: se
Out[37]:
a 1.00
b 6.00
c 6.00
d 3.25
e 4.00
dtype: float64
注意:如果你的Series是显式的整数索引,那么s[1]
这样的取值操作会使用显式索引,而s[1:3]
这样的切片操作却会使用隐式索引。Pandas开发人员在历史中为这种问题头疼不已,但没办法,现在还是这么混乱。
In [21]: s = pd.Series(['a','b','c'], index=[1,3,5])
In [22]: s
Out[22]:
1 a
3 b
5 c
dtype: object
In [23]: s[1]
Out[23]: 'a'
In [24]: s[1:3]
Out[24]:
3 b
5 c
dtype: object
对于DataFrame这种二维表格,情况有点不太一样,请务必注意!
核心思维:在DataFrame中,优先按列操作!
In [38]: df = pd.DataFrame(np.arange(16).reshape(4,4),
index=list('abcd'),columns=['one','two','three','four'])
In [39]: df
Out[39]:
one two three four
a 0 1 2 3
b 4 5 6 7
c 8 9 10 11
d 12 13 14 15
In [40]: df['two'] # 对于DataFrame默认是按列索引
Out[40]:
a 1
b 5
c 9
d 13
Name: two, dtype: int32
In [41]: df['b'] # KeyError,不能直接按行索引
In [43]: df[['one','four']]
Out[43]:
one four
a 0 3
b 4 7
c 8 11
d 12 15
In [44]: df[:2] # 什么!切片的时候居然是按行进行!
Out[44]:
one two three four
a 0 1 2 3
b 4 5 6 7
In [46]: df['c':'d'] # 注意闭合区间
Out[46]:
one two three four
c 8 9 10 11
d 12 13 14 15
In [47]: df ['one':'three'] # 试图用列索引来切片,但明显后台按行索引去找了,没找到。
Out[47]:
Empty DataFrame
Columns: [one, two, three, four]
Index: []
In [48]: df < 5
Out[48]:
one two three four
a True True True True
b True False False False
c False False False False
d False False False False
In [49]: df[df<5] = 0 # 这一波操作和numpy很类似
In [50]: df
Out[50]:
one two three four
a 0 0 0 0
b 0 5 6 7
c 8 9 10 11
d 12 13 14 15
是不是觉得好难理解记忆?还是numpy那种索引方式更符合人的思维习惯?没关系,Pandas考虑到了这一点,提供了类似numpy的行+列的索引标签,也就是loc和iloc。这两者差一个字母i。后者是以隐含的整数索引值来索引的,前者则使用你指定的显式的索引来定位值。
In [50]: df
Out[50]:
one two three four
a 0 0 0 0
b 0 5 6 7
c 8 9 10 11
d 12 13 14 15
In [51]: df.loc['b', ['two','four']] # 使用显式索引值,用逗号分隔行和列参数
Out[51]:
two 5
four 7
Name: b, dtype: int32
In [53]: df.loc['b':, 'two':'four'] # 切片方式,注意区间
Out[53]:
two three four
b 5 6 7
c 9 10 11
d 13 14 15
In [54]: df.iloc[2, [3, 0, 1]] # 用隐含的整数索引检索,但是这个打印格式好别扭
Out[54]:
four 11
one 8
two 9
Name: c, dtype: int32
In [55]: df.iloc[2]
Out[55]:
one 8
two 9
three 10
four 11
Name: c, dtype: int32
In [56]: df.iloc[1:,2:3] # 注意区间
Out[56]:
three
b 6
c 10
d 14
In [57]: df.iloc[:,:3][df.three>5] # 先切片,再布尔判断
Out[57]:
one two three
b 0 5 6
c 8 9 10
d 12 13 14
提示:老版本的ix标签已经不提倡使用了。
算术和广播
Python运算符 | Pandas方法 |
---|---|
+ | add() |
- | sub()、subtract() |
* | mul()、multiply() / truediv()、div()、divide() |
// | floordiv() |
% | mod() |
** | pow() |
当对两个Series或者DataFrame对象进行算术运算的时候,返回的结果是两个对象的并集。如果存在某个索引不匹配时,将以缺失值NaN的方式体现,并对以后的操作产生影响。这类似数据库的外连接操作。
In [58]: s1 = pd.Series([4.2,2.6, 5.4, -1.9], index=list('acde'))
In [60]: s2 = pd.Series([-2.3, 1.2, 5.6, 7.2, 3.4], index= list('acefg'))
In [61]: s1
Out[61]:
a 4.2
c 2.6
d 5.4
e -1.9
dtype: float64
In [62]: s2
Out[62]:
a -2.3
c 1.2
e 5.6
f 7.2
g 3.4
dtype: float64
In [63]: s1+s2
Out[63]:
a 1.9
c 3.8
d NaN
e 3.7
f NaN
g NaN
dtype: float64
In [64]: s1-s2
Out[64]:
a 6.5
c 1.4
d NaN
e -7.5
f NaN
g NaN
dtype: float64
In [65]: s1* s2
Out[65]:
a -9.66
c 3.12
d NaN
e -10.64
f NaN
g NaN
dtype: float64
In [66]: df1 = pd.DataFrame(np.arange(9).reshape(3,3),columns=list('bcd'),index=['one','two','three'])
In [67]: df2 = pd.DataFrame(np.arange(12).reshape(4,3),columns=list('bde'),index=['two','three','five','six'])
In [68]: df1
Out[68]:
b c d
one 0 1 2
two 3 4 5
three 6 7 8
In [69]: df2
Out[69]:
b d e
two 0 1 2
three 3 4 5
five 6 7 8
six 9 10 11
In [70]: df1 + df2
Out[70]:
b c d e
five NaN NaN NaN NaN
one NaN NaN NaN NaN
six NaN NaN NaN NaN
three 9.0 NaN 12.0 NaN
two 3.0 NaN 6.0 NaN
其实,在上述过程中,为了防止NaN对后续的影响,很多时候我们要使用一些填充值:
In [71]: df1.add(df2, fill_value=0)
Out[71]:
b c d e
five 6.0 NaN 7.0 8.0
one 0.0 1.0 2.0 NaN
six 9.0 NaN 10.0 11.0
three 9.0 7.0 12.0 5.0
two 3.0 4.0 6.0 2.0
In [74]: df1.reindex(columns=df2.columns, fill_value=0) # 也可以这么干
Out[74]:
b d e
one 0 2 0
two 3 5 0
three 6 8 0
注意,这里填充的意思是,如果某一方有值,另一方没有的话,将没有的那方的值填充为指定的参数值。而不是在最终结果中,将所有的NaN替换为填充值。
类似add的方法还有:
- add:加法
- sub:减法
- div:除法
- floordiv:整除
- mul:乘法
- pow:幂次方
DataFrame也可以和Series进行操作,这类似于numpy中不同维度数组间的操作,其中将使用广播机制。我们先看看numpy中的机制:
In [75]: a = np.arange(12).reshape(3,4)
In [76]: a
Out[76]:
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11]])
In [78]: a[0] # 取a的第一行,这是一个一维数组
Out[78]: array([0, 1, 2, 3])
In [79]: a - a[0] # 二维数组减一维数组,在行方向上进行了广播
Out[79]:
array([[0, 0, 0, 0],
[4, 4, 4, 4],
[8, 8, 8, 8]])
DataFrame和Series之间的操作是类似的:
In [80]: df = pd.DataFrame(np.arange(12).reshape(4,3),columns=list('bde'),index=['one','two','three','four'])
In [81]: s = df.iloc[0] # 取df的第一行生成一个Series
In [82]: df
Out[82]:
b d e
one 0 1 2
two 3 4 5
three 6 7 8
four 9 10 11
In [83]: s
Out[83]:
b 0
d 1
e 2
Name: one, dtype: int32
In [84]: df - s # 减法会广播
Out[84]:
b d e
one 0 0 0
two 3 3 3
three 6 6 6
four 9 9 9
In [85]: s2 = pd.Series(range(3), index=list('bef'))
In [86]: df + s2 # 如果存在不匹配的列索引,则引入缺失值
Out[86]:
b d e f
one 0.0 NaN 3.0 NaN
two 3.0 NaN 6.0 NaN
three 6.0 NaN 9.0 NaN
four 9.0 NaN 12.0 NaN
In [87]: s3 = df['d'] # 取df的一列
In [88]: s3
Out[88]:
one 1
two 4
three 7
four 10
Name: d, dtype: int32
In [89]: df.sub(s3, axis='index') # 指定按列进行广播
Out[89]:
b d e
one -1 0 1
two -1 0 1
three -1 0 1
four -1 0 1
在上面最后的例子中,我们通过axis='index'或者axis=0,在另外一个方向广播。
函数和映射
一些Numpy的通用函数对Pandas对象也有效:
In [91]: df = pd.DataFrame(np.random.randn(4,3), columns=list('bde'),index = ['one','two','three','four'])
In [92]: df
Out[92]:
b d e
one -0.522310 0.636599 0.992393
two 0.572624 -0.451550 -1.935332
three 0.021926 0.056706 -0.267661
four -2.718122 -0.740140 -1.565448
In [93]: np.abs(df)
Out[93]:
b d e
one 0.522310 0.636599 0.992393
two 0.572624 0.451550 1.935332
three 0.021926 0.056706 0.267661
four 2.718122 0.740140 1.565448
当然,你也可以自定义处理函数,然后使用pandas提供的apply方法,将它应用在每一列:
In [94]: f = lambda x: x.max() - x.min()
In [95]: df.apply(f)
Out[95]:
b 3.290745
d 1.376740
e 2.927725
dtype: float64
当然,可以指定按行应用f,只需要设置axis='columns'。也可以将引用函数的返回值设计为一个Series,这样最终结果会是个DataFrame:
In [96]: df.apply(f, axis='columns')
Out[96]:
one 1.514703
two 2.507956
three 0.324367
four 1.977981
dtype: float64
In [97]: def f2(x):
...: return pd.Series([x.min(),x.max()], index=['min','max'])
In [98]: df.apply(f2)
Out[98]:
b d e
min -2.718122 -0.740140 -1.935332
max 0.572624 0.636599 0.992393
还有更细粒度的apply方法,也就是DataFrame的applymap以及Series的map。它们逐一对每个元素进行操作,而不是整行整列的操作。请体会下面的例子:
In [99]: f3 = lambda x: '%.2f' % x
In [100]: df.applymap(f3)
Out[100]:
b d e
one -0.52 0.64 0.99
two 0.57 -0.45 -1.94
three 0.02 0.06 -0.27
four -2.72 -0.74 -1.57
In [101]: df['d'].map(f3) # 获取d列,这是一个Series
Out[101]:
one 0.64
two -0.45
three 0.06
four -0.74
Name: d, dtype: object
排序和排名
排序分两种:根据索引排序和根据元素值排序
根据索引排序使用的是sort_index方法,它返回一个新的排好序的对象:
In [103]: s = pd.Series(range(4),index = list('dabc'))
In [104]: s
Out[104]:
d 0
a 1
b 2
c 3
dtype: int64
In [105]: s.sort_index() # 根据索引的字母序列排序
Out[105]:
a 1
b 2
c 3
d 0
dtype: int64
In [108]: df = pd.DataFrame(np.random.randint(10,size=(4,3)), columns=list('edb'),index = ['two','one','five','four'])
In [109]: df
Out[109]:
e d b
two 7 6 1
one 5 6 8
five 8 4 1
four 7 0 3
In [110]: df.sort_index() # 默认按行索引排序,并以字母顺序
Out[110]:
e d b
five 8 4 1
four 7 0 3
one 5 6 8
two 7 6 1
In [111]: df.sort_index(axis=1) # 指定按列排序
Out[111]:
b d e
two 1 6 7
one 8 6 5
five 1 4 8
four 3 0 7
In [112]: df.sort_index(axis=1,ascending=False) # 默认升序,可以指定为倒序
Out[112]:
e d b
two 7 6 1
one 5 6 8
five 8 4 1
four 7 0 3
如果要根据某行或某列元素的值的大小进行排序,就要使用sort_values方法:
In [113]: s= pd.Series([4, 7,-3,2])
In [114]: s.sort_values()
Out[114]:
2 -3
3 2
0 4
1 7
dtype: int64
# np.nan缺失值会自动排到最后
In [115]: s2 = pd.Series([4, np.nan,7,np.nan,-3,2])
In [116]: s2
Out[116]:
0 4.0
1 NaN
2 7.0
3 NaN
4 -3.0
5 2.0
dtype: float64
In [117]: s2.sort_values()
Out[117]:
4 -3.0
5 2.0
0 4.0
2 7.0
1 NaN
3 NaN
dtype: float64
In [118]: df2 = pd.DataFrame({'b':[4,7,-3,2], 'a':[0,1,0,1]})
In [120]: df2
Out[120]:
b a
0 4 0
1 7 1
2 -3 0
3 2 1
In [121]: df2.sort_values(by='b') # 根据某一列里的元素值进行排序
Out[121]:
b a
2 -3 0
3 2 1
0 4 0
1 7 1
In [122]: df2.sort_values(by=['a','b']) # 根据某些列进行排序
Out[122]:
b a
2 -3 0
0 4 0
3 2 1
1 7 1
除了排序,还有排名。Pandas的排名规则不太好理解,其规则如下:
- 以升序排名为例
- 所有数中最小的数排为1.0
- 按数大小依此类推,2.0、3.0、4.0给安排位次
- 如果有重复的数,则重复的排名相加除以重复的个数,得出一个排名
- 重复的数后面的排名,接着排
比如下面的例子:
In [123]: s = pd.Series([7,-5,7,4,2,0,4])
In [124]: s.rank()
Out[124]:
0 6.5
1 1.0
2 6.5
3 4.5
4 3.0
5 2.0
6 4.5
dtype: float64
- -5最小,给排名1.0
- 0其次,排2.0
- 2再次,排3.0
- 有2个4,于是4.0+5.0等于9,再除个数2,最终排名4.5。4.0和5.0两个排名并未使用。
- 又有2个7,于是6.0+7.0等于13,再除2,最后排名6.5
也可以根据观察顺序进行排名位次分配,主要是重复的数,按顺序先后给排名,揣摩一下就能明白规律:
In [125]: s.rank(method='first')
Out[125]:
0 6.0
1 1.0
2 7.0
3 4.0
4 3.0
5 2.0
6 5.0
dtype: float64
还可以按最大值排名,并降序排列:
In [126]: s.rank(method='max',ascending=False)
Out[126]:
0 2.0
1 7.0
2 2.0
3 4.0
4 5.0
5 6.0
6 4.0
dtype: float64
DataFrame则可以根据行或列计算排名:
In [128]: df = pd.DataFrame(np.random.randint(-10,10,(4,3)),columns=list('abc'))
In [129]: df
Out[129]:
a b c
0 -9 -1 -8
1 -10 8 -7
2 -4 2 6
3 9 -2 -7
In [130]: df.rank(axis='columns')
Out[130]:
a b c
0 1.0 3.0 2.0
1 1.0 3.0 2.0
2 1.0 2.0 3.0
3 3.0 2.0 1.0
下面列出了method参数可以使用的排名方法:
- average:默认方式,计算平均排名
- min:最小排名
- max:最大排名
- first:观察顺序排名
- dense:类似min,但组间排名总是增加1
统计和汇总
Pandas也有一套和Numpy类似的数学、统计学方法。不过在使用中要注意的是,Numpy通常将数组看作一个整体,而Pandas通常对列进行操作。当然,这两者也能单独对行进行操作。另外,Pandas内建了处理缺失值的功能,这一点是numpy不具备的。
In [131]: df = pd.DataFrame([[1.4, np.nan],[7.1,-4.2],[np.nan,np.nan],[0.75,-1.1]],index=list('abcd'),columns=['one','two'])
In [132]: df
Out[132]:
one two
a 1.40 NaN
b 7.10 -4.2
c NaN NaN
d 0.75 -1.1
In [133]: df.sum() # 默认对列进行求和,并返回一个Series对象,缺失值默认被忽略
Out[133]:
one 9.25
two -5.30
dtype: float64
In [134]: df.sum(axis='columns') # 指定对行进行求和
Out[134]:
a 1.40
b 2.90
c 0.00
d -0.35
dtype: float64
In [135]: df.mean(axis='columns', skipna=False) # 对行求平均值,但不忽略缺失值
Out[135]:
a NaN
b 1.450
c NaN
d -0.175
dtype: float64
下面是主要的统计和汇总方法:
方法 | 描述 |
---|---|
min | 最小值 |
max | 最大值 |
idxmin | 返回某行或某列最小值的索引 |
idxmax | 最大值的索引 |
cumsum | 累加 |
cumprod | 累乘 |
count | 统计非NaN的个数 |
describe | 汇总统计集合 |
quantile | 计算样本的从0到1间的分位数 |
sum | 求和 |
mean | 平均值 |
median | 中位数(50%) |
mad | 平均值的平均绝对偏差 |
prod | 所有值的积 |
var | 方差 |
std | 标案差 |
skew | 样本偏度,第三时刻值 |
kurt | 样本峰度,第四时刻值 |
diff | 计算第一个算术差值 |
pct_change | 计算百分比 |
In [136]: df.idxmax()
Out[136]:
one b
two d
dtype: object
In [137]: df.idxmin()
Out[137]:
one d
two b
dtype: object
In [138]: df.cumsum()
Out[138]:
one two
a 1.40 NaN
b 8.50 -4.2
c NaN NaN
d 9.25 -5.3
In [139]: df.cumprod()
Out[139]:
one two
a 1.400 NaN
b 9.940 -4.20
c NaN NaN
d 7.455 4.62
In [144]: df.count()
Out[144]:
one 3
two 2
dtype: int64
最重要的describe方法:
In [140]: df.describe()
Out[140]:
one two
count 3.000000 2.000000
mean 3.083333 -2.650000
std 3.493685 2.192031
min 0.750000 -4.200000
25% 1.075000 -3.425000
50% 1.400000 -2.650000
75% 4.250000 -1.875000
max 7.100000 -1.100000
In [141]: s=pd.Series(list('aabc'*4))
In [143]: s.describe() # 对于非数值型,统计类型不一样
Out[143]:
count 16
unique 3
top a
freq 8
dtype: object
还有几个非常重要的方法:
- unique:计算唯一值数组,并按观察顺序返回
- value_counts:计数,并按多到少排序
- isin:判断是否包含成员
In [147]: s = pd.Series(list('cadaabbcc'))
In [149]: uniques = s.unique() # 获取去重后的值
In [150]: uniques # 这是一个array数组
Out[150]: array(['c', 'a', 'd', 'b'], dtype=object)
In [151]: s.value_counts() # 计数,默认从多到少排序
Out[151]:
a 3
c 3
b 2
d 1
dtype: int64
In [152]: pd.value_counts(s, sort=False) # 也可以这么调用,并且不排序
Out[152]:
d 1
c 3
a 3
b 2
dtype: int64
In [155]: mask = s.isin(['b','c']) # 判断Series的元素在不在b和c里面
In [156]: mask # 这是一个bool类型
Out[156]:
0 True
1 False
2 False
3 False
4 False
5 True
6 True
7 True
8 True
dtype: bool
In [157]: s[mask]
Out[157]:
0 c
5 b
6 b
7 c
8 c
dtype: object
文件读取
通过手动输入数据来生成Series和DataFrame对象。在实际工作中,这显然是不可能的。大多数时候,我们都要与外部进行数据交换,输入和输出。有很多的工具可以帮助我们读取和写入各种格式的数据,但是Pandas自己提供的读写工具更方便更适合。
数据交换主要有以下几种类型:
- 读取文本文件或磁盘上的其它高效文件格式
- 与数据库交互
- 与网络资源,比如Web API进行交互
1、 文本格式数据的读取
在Pandas的使用场景中,最多的是将表格型的数据读取为DataFrame对象。实现这一功能的函数有很多,最常用的是read_csv
和read_table
。
下表列出了pandas主要的读写函数:
函数 | 说明 |
---|---|
read_csv | 读取默认以逗号作为分隔符的文件 |
read_table | 读取默认以制表符分隔的文件 |
read_fwf | 从特定宽度格式的文件中读取数据(无分隔符) |
read_clipboard | read_table的剪贴板版本 |
read_excel | 从EXCEL的XLS或者XLSX文件中读取数据 |
read_hdf | 读取用pandas存储的HDF5文件 |
read_html | 从HTML文件中读取所有表格数据 |
read_json | 从JSON字符串中读取数据 |
read_msgpack | 读取MessagePack格式存储的任意对象 |
read_pickle | 读取以Python Pickle格式存储的对象 |
read_sas | 读取SAS系统中定制存储格式的数据集 |
read_sql | 将SQL查询的结果读取出来 |
read_stata | 读取stata格式的数据集 |
read_feather | 读取Feather二进制格式 |
Pandas不但有这么多的读取函数,每个函数还有大量的可设置参数。比如read_csv到目前就有超过50个参数。这些函数和参数是如此的复杂,很难有人能够将它们全部记住,所以当你在读取文件时遇到了困难,请前往官方文档,那里的一些示例可能对你有帮助。
让我们来看看一些具体的例子,首先有这么一个文件:
# ex1.csv
a,b,c,d,message
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo
可以使用read_csv方法读取它,默认第一行为列索引,自动生成了行索引:
In [14]: df = pd.read_csv('d:/ex1.csv')
In [15]: df
Out[15]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
也可以使用read_table函数,并指定分隔符为逗号:
In [17]: pd.read_table('d:/ex1.csv',sep=',')
Out[17]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
对于不包含表头行的文件,比如下面的ex2,要么使用默认列名,要么我们指定列名:
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo
注意指定列名用的参数名是names,不是columns:
In [18]: pd.read_csv('d:/ex2.csv',header=None) # 使用默认列名
Out[18]:
0 1 2 3 4
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
In [19]: pd.read_csv('d:/ex2.csv', names=['a','b','c','d','message']) # 自定义
Out[19]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
如果你想将上面的message列当作DataFrame的行索引,可以将它传递给参数index_col:
# 注意其中的names依然必须包含message字符串,否则会弹出KeyError
In [20]: pd.read_csv('d:/ex2.csv', names=['a','b','c','d','message'],index_col='message')
Out[20]:
a b c d
message
hello 1 2 3 4
world 5 6 7 8
foo 9 10 11 12
如果想读取成分层索引,则需要为index_col参数传入一个包含列序号或列名的列表:
# csv_mindex.csv
key1,key2,value1,value2
one,a,1,2
one,b,3,4
one,c,5,6
one,d,7,8
two,a,9,10
two,b,11,12
two,c,13,14
two,d,15,16
分层索引类似多级索引,后面会介绍。
In [22]: df = pd.read_csv('d:/csv_mindex.csv', index_col=['key1','key2'])
In [23]: df
Out[23]:
value1 value2
key1 key2
one a 1 2
b 3 4
c 5 6
d 7 8
two a 9 10
b 11 12
c 13 14
d 15 16
对于一些更复杂,或者说更不规整的文件,比如分隔符不固定的情况,需要有更多的处理技巧,比如下面这个文件,以不同数量的空格分隔:
# ex3.txt
A B C
aaa -0.264438 -1.026059 -0.619500
bbb 0.927272 0.302904 -0.032399
ccc -0.264273 -0.386314 -0.217601
ddd -0.871858 -0.348382 1.100491
这时候需要传入一个正则表达式作为分隔符参数:
In [25]: result = pd.read_table('d:/ex3.txt', sep='\s+')
In [26]: result
Out[26]:
A B C
aaa -0.264438 -1.026059 -0.619500
bbb 0.927272 0.302904 -0.032399
ccc -0.264273 -0.386314 -0.217601
ddd -0.871858 -0.348382 1.100491
上面的正则表达式是\s+
。并且由于列名比数据的列数少一个,系统自动推断第一列应当作为行索引。
大多数读取函数都带有很多帮助我们处理各种异常情况的参数,比如可以使用skiprows来跳过数据中的指定行.
原始数据:
# hey!
a,b,c,d,message
# just wanted to make things more difficult for you
# who reads CSV files with computers, anyway?
1,2,3,4,hello
5,6,7,8,world
9,10,11,12,foo
我们需要跳过无用的第1、3、4行废话:
In [27]: pd.read_csv('d:/ex4.csv', skiprows=[0,2,3])
Out[27]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
缺失值的处理是读取数据时非常重要的部分。通常情况下,缺失值要么不显示,直接空着,要么用一些标识值表示,比如NA或NULL。
看下面的数据,注意第三行的6和8之间有个空的:
something,a,b,c,d,message
one,1,2,3,4,NA
two,5,6,,8,world
three,9,10,11,12,foo
read_csv
会将缺失值自动读成NaN:
In [30]: result = pd.read_csv('d:/ex5.csv')
In [31]: result
Out[31]:
something a b c d message
0 one 1 2 3.0 4 NaN
1 two 5 6 NaN 8 world
2 three 9 10 11.0 12 foo
In [32]: pd.isnull(result)
Out[32]:
something a b c d message
0 False False False False False True
1 False False False True False False
2 False False False False False False
我们可以额外指定na_values
参数,将某些值也当作缺失值来看待,比如下面我们将3.0也当作缺失值对待:
In [40]: result = pd.read_csv('d:/ex5.csv',na_values=[3.0])
In [41]: result
Out[41]:
something a b c d message
0 one 1 2 NaN 4 NaN
1 two 5 6 NaN 8 world
2 three 9 10 11.0 12 foo
甚至可以对不同的列,指定不同的缺失值标识:
In [42]: f = {'message':['foo','NA'],'something':['two']}
In [43]: result = pd.read_csv('d:/ex5.csv',na_values=f)
In [44]: result
Out[44]:
something a b c d message
0 one 1 2 3.0 4 NaN
1 NaN 5 6 NaN 8 world
2 three 9 10 11.0 12 NaN
下表列出了read_csv
和read_table
函数的一些常用参数:
参数 | 说明 |
---|---|
path | 文件路径 |
sep | 指定分隔符或正则表达式 |
header | 用作列名的行号 |
index_col | 用作行索引的列名或列号 |
names | 结果的列名列表 |
skiprows | 从起始处,需要跳过的行 |
na_values | 需要用NaN替换的值 |
comment | 在行结尾处分隔注释的字符 |
parse_dates | 尝试将数据解析为datetime,默认是False |
keep_date_col | 如果连接列到解析日期上,保留被连接的列,默认False |
converters | 包含列名称映射到函数的字典 |
dayfirst | 解析非明确日期时,按国际格式处理 |
date_parser | 用于解析日期的函数 |
nrows | 从文件开头处读入的行数 |
iterator | 返回一个TextParser对象,用于零散地读入文件 |
chunksize | 用于迭代的块大小 |
skip_footer | 忽略文件尾部的行数 |
verbose | 打印各种解析器输出的信息 |
encoding | 文本编码,比如UTF-8 |
thousands | 定义千位分隔符,例如逗号或者圆点 |
2、分块读取
当我们处理大型文件的时候,读入文件的一个小片段或者按小块遍历文件是比较好的做法。
在这之前,我们最好先对Pandas的显示设置进行调整,使之更为紧凑:
In [45]: pd.options.display.max_rows = 10
这样,即使是大文件,最多也只会显式10行具体内容。
In [46]: result = pd.read_csv('d:/ex6.csv')
In [47]: result
Out[47]:
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
... ... ... ... ... ..
9995 2.311896 -0.417070 -1.409599 -0.515821 L
9996 -0.479893 -0.650419 0.745152 -0.646038 E
9997 0.523331 0.787112 0.486066 1.093156 K
9998 -0.362559 0.598894 -1.843201 0.887292 G
9999 -0.096376 -1.012999 -0.657431 -0.573315 0
[10000 rows x 5 columns]
或者使用nrows参数,指明从文件开头往下只读n行:
In [48]: result = pd.read_csv('d:/ex6.csv',nrows=5)
In [49]: result
Out[49]:
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
或者指定chunksize作为每一块的行数,分块读入文件:
In [50]: chunker = pd.read_csv('d:/ex6.csv', chunksize=1000)
In [51]: chunker
Out[51]: <pandas.io.parsers.TextFileReader at 0x2417d6cfb38>
上面的TextFileReader对象是一个可迭代对象。例如我们可以遍历它,并对‘key’列进行聚合获得计数值:
In [52]: total = pd.Series([])
In [53]: for piece in chunker:
...: total = total.add(piece['key'].value_counts(), fill_value=0)
...: total = total.sort_values(ascending=False)
In [54]: total
Out[54]:
E 368.0
X 364.0
L 346.0
O 343.0
Q 340.0
...
5 157.0
2 152.0
0 151.0
9 150.0
1 146.0
Length: 36, dtype: float64
In [55]: total[:10]
Out[55]:
E 368.0
X 364.0
L 346.0
O 343.0
Q 340.0
M 338.0
J 337.0
F 335.0
K 334.0
H 330.0
dtype: float64
3、写出数据
既然有读,必然有写。
可以使用DataFrame的to_csv方法,将数据导出为逗号分隔的文件:
In [57]: result
Out[57]:
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
In [58]: result.to_csv('d:/out.csv')
当然 ,也可以指定为其它分隔符,甚至将数据输出到sys.stdout中:
In [60]: result.to_csv(sys.stdout, sep='|')
|one|two|three|four|key
0|0.467976300189|-0.0386485396255|-0.295344251987|-1.82472622729|L
1|-0.358893469543|1.40445260007|0.704964644926|-0.20063830401500002|B
2|-0.50184039929|0.659253707223|-0.42169061931199997|-0.0576883018364|G
3|0.20488621220199998|1.07413396504|1.38836131252|-0.982404023494|R
4|0.354627914484|-0.13311585229599998|0.283762637978|-0.837062961653|Q
缺失值默认以空字符串出现,当然也可以指定其它标识值对缺失值进行标注,比如使用‘NULL’:
In [70]: data = pd.DataFrame(np.random.randint(9,size=9).reshape(3,3))
In [71]: data
Out[71]:
0 1 2
0 7 7 3
1 8 1 5
2 2 4 2
In [72]: data.iloc[2,2] = np.nan
In [73]: data.to_csv(sys.stdout, na_rep='NULL')
,0,1,2
0,7,7,3.0
1,8,1,5.0
2,2,4,NULL
在写入的时候,我们还可以禁止将行索引和列索引写入:
In [74]: result.to_csv(sys.stdout, index=False, header=False)
0.467976300189,-0.0386485396255,-0.295344251987,-1.82472622729,L
-0.358893469543,1.40445260007,0.704964644926,-0.20063830401500002,B
-0.50184039929,0.659253707223,,-0.0576883018364,G
0.20488621220199998,1.07413396504,1.38836131252,-0.982404023494,R
0.354627914484,-0.13311585229599998,0.283762637978,-0.837062961653,Q
也可以挑选需要的列写入:
In [75]: result.to_csv(sys.stdout, index=False, columns=['one','three','key'])
one,three,key
0.467976300189,-0.295344251987,L
-0.358893469543,0.704964644926,B
-0.50184039929,,G
0.20488621220199998,1.38836131252,R
0.354627914484,0.283762637978,Q
Series的写入方式也是一样的:
In [76]: dates = pd.date_range('1/1/2019', periods=7) # 生成一个日期Series
In [77]: dates
Out[77]:
DatetimeIndex(['2019-01-01', '2019-01-02', '2019-01-03', '2019-01-04',
'2019-01-05', '2019-01-06', '2019-01-07'],
dtype='datetime64[ns]', freq='D')
In [78]: ts = pd.Series(np.arange(7), index=dates) # 将日期作为索引
In [79]: ts
Out[79]:
2019-01-01 0
2019-01-02 1
2019-01-03 2
2019-01-04 3
2019-01-05 4
2019-01-06 5
2019-01-07 6
Freq: D, dtype: int32
In [80]: ts.to_csv('d:/tseries.csv') # 写入文件中
JSON和Pickle
假设有如下的JSON文件:
[{"a": 1, "b": 2, "c": 3},
{"a": 4, "b": 5, "c": 6},
{"a": 7, "b": 8, "c": 9}]
使用read_json函数可以自动将JSON数据集按照指定的顺序转换为Series或者DataFrame对象,其默认做法是假设JSON数据中的每个对象是表里的一行:
In [81]: data = pd.read_json('d:/example.json')
In [82]: data
Out[82]:
a b c
0 1 2 3
1 4 5 6
2 7 8 9
反之,使用to_json函数,将pandas对象转换为json格式:
In [83]: print(data.to_json())
{"a":{"0":1,"1":4,"2":7},"b":{"0":2,"1":5,"2":8},"c":{"0":3,"1":6,"2":9}}
In [84]: print(data.to_json(orient='records')) # 与上面的格式不同
[{"a":1,"b":2,"c":3},{"a":4,"b":5,"c":6},{"a":7,"b":8,"c":9}]
我们都知道,Python标准库pickle,可以支持二进制格式的文件读写,且高效方便。
pandas同样设计了用于pickle格式的读写函数read_pickle
和to_pickle
。
In [85]: df = pd.read_csv('d:/ex1.csv')
In [86]: df
Out[86]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
In [87]: df.to_pickle('d:/df_pickle')
In [88]: new_df = pd.read_pickle('d:/df_pickle')
In [89]: new_df
Out[89]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
HDF5
HDF5是一个备受好评的文件格式,广泛用于存储大量的科学计算数组。很多数据分析的实际工作中困难都在于I/O密集,而不是CPU密集,使用HDF5这样的工具可以大大加速你的应用。它以C库的形式提供,并且具有许多其它语言的接口,包括Java、MATLAB和Python。HDF5中的HDF代表分层数据格式。每个HDF5文件可以存储多个数据集并且支持元数据。支持多种压缩模式的即时压缩,使得重复模式的数据可以更高效地存储。HDF5适用于处理不适合在内存中存储地超大型数据,可以使你高效读写大型数据的一小块。
要注意的是HDF5并不是数据库,它是一种适合一次写入多次读取的数据集。尽管数据可以在任何时间添加到文件中,但如果多个写入者持续写入,文件可能会损坏。
在外部,可以通过使用PyTables或h5py等库直接访问HDF5文件,但Pandas提供了一个高阶的接口,可以简化存储过程。
Pandas使用HDFStore类来实现这一功能,类似字典一样的工作方式:
In [90]: df = pd.DataFrame({'a':np.random.randn(100)}) # 有100行
In [91]: df.head() # 看看前5行
Out[91]:
a
0 -0.917062
1 0.797327
2 0.659787
3 -0.779638
4 0.550464
In [92]: store = pd.HDFStore('mydata.h5') # 生成HDF5文件
In [93]: store['obj1'] = df # 以类似字典的方式,向文件里写入内容
In [94]: store['obj1_col'] = df['a'] # 再写一点
In [95]: store # 看看信息,在当前工作目录下,你可以找到这个文件
Out[95]:
<class 'pandas.io.pytables.HDFStore'>
File path: mydata.h5
既然是类似字典的工作方式,那当然也可以像字典那样索引数据:
In [97]: store['obj1']
Out[97]:
a
0 -0.917062
1 0.797327
2 0.659787
3 -0.779638
4 0.550464
.. ...
95 -2.042226
96 1.286631
97 0.487709
98 -0.202580
99 1.619085
[100 rows x 1 columns]
HDFStore支持两种工作模式,‘fixed’和‘table’。table的速度更慢,但支持一种类似数据库SQL语言的查询操作:
In [98]: store.put('obj2',df,format='table') # put是赋值的显式版本,允许我们设置其它选项
In [99]: store.select('obj2', where=['index >=10 and index <= 15']) # 类似SQl语言的查询操作,要注意空格的位置
Out[99]:
a
10 -1.430696
11 -0.616732
12 -0.643991
13 -0.004270
14 0.797136
15 -0.175095
In [100]: store.close() # 关闭文件
除此之外,Padas还提供了以上操作的快捷方式:
In [101]: df.to_hdf('mydata.h5','obj3', format='table')
In [102]: pd.read_hdf('mydata.h5', 'obj3' ,where=['index < 5'])
Out[102]:
a
0 -0.917062
1 0.797327
2 0.659787
3 -0.779638
4 0.550464
EXCEL文件
Pandas支持Excle 2003或更高版本文件的读写。在内部,这需要使用附加包xlrd和openpyxl来分别读取XLS和XLSX文件。如果你的环境中没有这两个包,可能需要使用pip或者conda手动安装一下。
使用很简单,看下面的例子:
In [104]: xlsx = pd.ExcelFile('d:/ex1.xlsx') # 打开excel文件
In [105]: pd.read_excel(xlsx, 'Sheet1') # 读取指定的表
Out[105]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
或者使用更简洁的语法:
In [106]: df = pd.read_excel('d:/ex1.xlsx', 'Sheet1')
In [107]: df
Out[107]:
a b c d message
0 1 2 3 4 hello
1 5 6 7 8 world
2 9 10 11 12 foo
将pandas数据写回到excel文件中时,你必须先生成一个ExcelWriter,然后使用to_excel方法将数据写入:
In [108]: writer = pd.ExcelWriter('d:/ex2.xlsx') # 生成文件
In [109]: df.to_excel(writer, 'Sheet1') # 写入
In [110]: writer.save() #关闭文件
当然,也可以使用快捷操作:
In [112]: df.to_excel('d:/ex3.xlsx')
Web交互
很多网站都有公开的API,通过JSON或者其它格式提供数据服务。我们可以利用Python的requests库来访问这些API。Anaconda环境中默认已经安装好了requests,如果你的环境中,没有安装这个包的话,可以通过pip或者conda进行安装:
pip install requests
conda install requests
下面我们以获取Github上最新的30条关于pandas的问题为例,介绍如何与Web API交互。
In [113]: import requests # 导入包
In [114]: url = 'https://api.github.com/repos/pandas-dev/pandas/issues' # 要访问的url
In [115]: response = requests.get(url) # 访问页面,需要等待一会
In [116]: response # 成功获取响应的数据
Out[116]: <Response [200]>
In [117]: data = response.json() # 解析为json格式
In [118]: data[0]['title'] # 查看第一个问题的标题
Out[118]: 'python-project-packaging-with-pandas'
data中的每个元素都是一个包含Github问题页面上的所有数据的字典(注释除外)。
In [119]: data[0]
Out[119]:
{'url': 'https://api.github.com/repos/pandas-dev/pandas/issues/24779',
'repository_url': 'https://api.github.com/repos/pandas-dev/pandas',
'labels_url': 'https://api.github.com/repos/pandas-dev/pandas/issues/24779/labels{/name}',
'comments_url': 'https://api.github.com/repos/pandas-dev/pandas/issues/24779/comments',
'events_url': 'https://api.github.com/repos/pandas-dev/pandas/issues/24779/events',
'html_url': 'https://github.com/pandas-dev/pandas/issues/24779',
'id': 399193081,
'node_id': 'MDU6SXNzdWUzOTkxOTMwODE=',
'number': 24779,
'title': 'python-project-packaging-with-pandas',
......后面省略
我们可以将data直接传给DataFrame,并提取感兴趣的字段:
In [120]: issues = pd.DataFrame(data, columns= ['number','title','labels','state'])
In [121]: issues
Out[121]:
number ... state
0 24779 ... open
1 24778 ... open
2 24776 ... open
3 24775 ... open
4 24774 ... open
.. ... ... ...
25 24736 ... open
26 24735 ... open
27 24733 ... open
28 24732 ... open
29 24730 ... open
上面我们截取了'number','title','labels','state'四个字段的内容,并构造了DataFrame对象,下面就可以利用pandas对它进行各种操作了!
数据库交互
在实际使用场景中,大部分数据并不是存储在文本或者Excel文件中的,而是一些基于SQL语言的关系型数据库中,比如MySQL。
从SQL中将数据读取为DataFrame对象是非常简单直接的,pandas提供了多个函数用于简化这个过程。
下面以Python内置的sqlite3标准库为例,介绍一下操作过程。
首先是生成数据库:
In [123]: import sqlite3 # 导入这个标准内置库
# 编写一条创建test表的sql语句
In [124]: query = """
...: CREATE TABLE test
...: (a VARCHAR(20), b VARCHAR(20), c REAL, d integer);"""
In [125]: con = sqlite3.connect('mydata.sqlite') # 创建数据库文件,并连接
In [126]: con.execute(query) # 执行sql语句
Out[126]: <sqlite3.Cursor at 0x2417e5535e0>
In [127]: con.commit # 提交事务
Out[127]: <function Connection.commit>
再插入几行数据:
# 两个人和一只老鼠的信息
In [129]: data = [('tom', 'male',1.75, 20),
...: ('mary','female',1.60, 18),
...: ('jerry','rat', 0.2, 60)]
...:
# 再来一条空数据
In [130]: stmt = "INSERT INTO test VALUES(?,?,?,?)"
In [131]: con.executemany(stmt,data) # 执行多个语句
Out[131]: <sqlite3.Cursor at 0x2417e4b9f80>
In [132]: con.commit() # 再次提交事务
前面都是往数据库里写入内容,下面我们来读数据:
In [133]: cursor = con.execute('select * from test') # 执行查询语句
In [134]: rows = cursor.fetchall() # 获取查询结果
In [135]: rows
Out[135]:
[('tom', 'male', 1.75, 20),
('mary', 'female', 1.6, 18),
('jerry', 'rat', 0.2, 60)]
In [136]: cursor.description # 看看结果中的列名,其实就是数据表的列名
Out[136]:
(('a', None, None, None, None, None, None),
('b', None, None, None, None, None, None),
('c', None, None, None, None, None, None),
('d', None, None, None, None, None, None))
In [137]: pd.DataFrame(rows,columns= [x[0] for x in cursor.description])
Out[137]:
a b c d
0 tom male 1.75 20
1 mary female 1.60 18
2 jerry rat 0.20 60
上面最后生成DataFrame时,使用了一个列表推导式来构成列名序列。
例子到这里大致就完成了,但是关于数据库的连接和查询操作实在是够繁琐的,你肯定不想每次都这么来一遍。那么怎么办呢?使用流行的Python的SQL工具包SQLAlchemy,它能简化你的数据库操作。同时,pandas提供了一个read_sql函数,允许你从通用的SQLAlchemy连接中轻松地读取数据。一个典型的操作如下:
In [138]: import sqlalchemy as sqla
In [140]: db = sqla.create_engine('sqlite:///mydata.sqlite') # 创建连接
In [141]: pd.read_sql('select * from test', db) # 查询数据并转换为pandas对象
Out[141]:
a b c d
0 tom male 1.75 20
1 mary female 1.60 18
2 jerry rat 0.20 60
在Anaconda中,已经默认安装了SQLAlchemy,可以直接使用。如果你的环境中没有SQLAlchemy,请自定安装,或者搜索教程进行学习。
处理缺失值
在进行数据分析和建模的过程中,我们80%的时间往往花在数据准备上:加载、清理、转换、处理和重新排列。为了提高这一过程的效率,Pandas提供了一系列的高级、灵活和快速的工具集,配合Python语言内置的处理功能,可以满足绝大多数场景下的使用需求。
Pandas中,使用numpy.nan标识缺失值,在打印的时候,经常以空字符串、NA、NaN、NULL等形式出现。Python内置的None值也被当作缺失值处理。但合法的缺失值只有NaN和None,另外一些我们自己定义的缺失值不属于语法上的,比如你将-999看作缺失值。
在Pandas中, None和NaN的区别:
- None被看作一个object对象,需要消耗更多的资源,处理速度更慢。不支持一些数学操作,因为None+数字是错误的语法。很多时候None会自动转换成NaN。
- NaN是float64类型,虽然名字叫做‘不是一个数’,但却属于数字类,可以进行数学运算不会报错,虽然所有和它进行计算的最终结果依然是NaN。它的运算速度更快,还支持全局性的操作。
以下是主要的缺失值处理方法:
- dropna:删除缺失值
- fillna: 用某些值填充缺失的数据或使用插值方法(比如ffill\bfill)
- isnull:判断哪些值是缺失值,返回布尔
- notnull:isnull的反函数
1、发现缺失值
Pandas 数据结构有两种有效的方法可以发现缺失值:isnull() 和 notnull()。每种方法都 返回布尔类型的掩码数据,例如:
In[13]: data = pd.Series([1, np.nan, 'hello', None])
In[14]: data.isnull()
Out[14]: 0 False
1 True
2 False
3 True
dtype: bool
布尔类型掩码数组可以直接作为 Series 或 DataFrame 的索引使用:
In[15]: data[data.notnull()]
Out[15]: 0 1
2 hello
dtype: object
在 Series 里使用的 isnull() 和 notnull() 同样适用于 DataFrame,产生的结果同样是布尔类型。
2、删除缺失值
通常使用dropna方法滤删除缺失值,默认它不直接修改数据,而是返回一个新对象。如果想原地修改,举一反三,请尝试inplace参数:
In [12]: from numpy import nan as NA # 导入惯例
In [13]: s = pd.Series([1, NA, 3.5, NA, 7])
In [14]: s
Out[14]:
0 1.0
1 NaN
2 3.5
3 NaN
4 7.0
dtype: float64
In [15]: s.dropna() # 本质上就是把缺失值删除
Out[15]:
0 1.0
2 3.5
4 7.0
dtype: float64
In [16]: s[s.notnull()] # 等同于上面的操作
Out[16]:
0 1.0
2 3.5
4 7.0
dtype: float64
在处理DataFrame对象的缺失值的时候,可能会复杂点。无法删除df的单个元素,只能整行整列的删除,dropna默认情况下会删除包含缺失值的行!为什么呢?因为DataFrame对象的每一行数据,在实际中,相当于一个样本,大量的样本中删除了一条,关系不大。但DataFrame对象的每一列相当于大量样本中共同的某个特征的值,如果删除了一个特征,那么对整个样本集的影响非常大。
In [20]: df = pd.DataFrame([[1, 6.5, 3],[1, NA, NA],[NA, NA, NA],[NA, 6.5,3]])
In [21]: df
Out[21]:
0 1 2
0 1.0 6.5 3.0
1 1.0 NaN NaN
2 NaN NaN NaN
3 NaN 6.5 3.0
In [22]: df.dropna() # 只剩1行了
Out[22]:
0 1 2
0 1.0 6.5 3.0
In [23]: df.dropna(how='all') # 只将整行都是缺失值的删除
Out[23]:
0 1 2
0 1.0 6.5 3.0
1 1.0 NaN NaN
3 NaN 6.5 3.0
In [24]: df[4] = NA # 新增一列全是缺失值
In [25]: df
Out[25]:
0 1 2 4
0 1.0 6.5 3.0 NaN
1 1.0 NaN NaN NaN
2 NaN NaN NaN NaN
3 NaN 6.5 3.0 NaN
In [26]: df.dropna(axis=1, how='all') # 指定以列的形式删除
Out[26]:
0 1 2
0 1.0 6.5 3.0
1 1.0 NaN NaN
2 NaN NaN NaN
3 NaN 6.5 3.0
注意上面axis和how参数的用法。
还可以通过thresh参数设置行或列中非缺失值的最小数量,在此数量以下的将被删除。
3、补全缺失值
所有数据都是宝贵的,大多数时候,我们不希望丢弃原始数据,而是补全缺失值。
fillna是补全缺失值的方法,为它提供一个固定值即可:
In [31]: df = pd.DataFrame(np.random.randn(7,3))
In [32]: df
Out[32]:
0 1 2
0 -0.229682 -0.483246 -0.063835
1 0.716649 1.593639 -1.364550
2 -1.362614 1.628310 -1.617992
3 1.128828 -1.120265 -0.657313
4 1.078143 1.136835 -0.427125
5 0.441696 0.219477 0.695700
6 -0.501183 1.453678 -2.734985
In [33]: df.iloc[:4, 1] = NA
In [35]: df.iloc[:2, 2] = NA
In [36]: df
Out[36]:
0 1 2
0 -0.229682 NaN NaN
1 0.716649 NaN NaN
2 -1.362614 NaN -1.617992
3 1.128828 NaN -0.657313
4 1.078143 1.136835 -0.427125
5 0.441696 0.219477 0.695700
6 -0.501183 1.453678 -2.734985
In [37]: df.fillna(0)
Out[37]:
0 1 2
0 -0.229682 0.000000 0.000000
1 0.716649 0.000000 0.000000
2 -1.362614 0.000000 -1.617992
3 1.128828 0.000000 -0.657313
4 1.078143 1.136835 -0.427125
5 0.441696 0.219477 0.695700
6 -0.501183 1.453678 -2.734985
也可以提供一个字典,为不同的列设定不同的填充值。
In [38]: df.fillna({1:1, 2:2})
Out[38]:
0 1 2
0 -0.229682 1.000000 2.000000
1 0.716649 1.000000 2.000000
2 -1.362614 1.000000 -1.617992
3 1.128828 1.000000 -0.657313
4 1.078143 1.136835 -0.427125
5 0.441696 0.219477 0.695700
6 -0.501183 1.453678 -2.734985
当然,fillna也不会原地修改数据,如果你想,请使用inplace参数:
In [39]: _ = df.fillna(0, inplace=True)
In [40]: df
Out[40]:
0 1 2
0 -0.229682 0.000000 0.000000
1 0.716649 0.000000 0.000000
2 -1.362614 0.000000 -1.617992
3 1.128828 0.000000 -0.657313
4 1.078143 1.136835 -0.427125
5 0.441696 0.219477 0.695700
6 -0.501183 1.453678 -2.734985
也可以使用ffill和bfill这种插值法填充缺失值:
In [41]: df = pd.DataFrame(np.random.randn(6,3))
In [42]: df.iloc[2:, 1]=NA
In [43]: df.iloc[4:, 2]=NA
In [44]: df
Out[44]:
0 1 2
0 -0.858762 0.083342 -0.315598
1 -0.211846 0.076648 1.188298
2 -0.513364 NaN 0.079216
3 0.398399 NaN -0.290225
4 -1.375898 NaN NaN
5 0.932812 NaN NaN
In [45]: df.fillna(method='ffill') # 使用前一个值进行填充
Out[45]:
0 1 2
0 -0.858762 0.083342 -0.315598
1 -0.211846 0.076648 1.188298
2 -0.513364 0.076648 0.079216
3 0.398399 0.076648 -0.290225
4 -1.375898 0.076648 -0.290225
5 0.932812 0.076648 -0.290225
In [46]: df.fillna(method='ffill',limit=2) # 限制填充次数
Out[46]:
0 1 2
0 -0.858762 0.083342 -0.315598
1 -0.211846 0.076648 1.188298
2 -0.513364 0.076648 0.079216
3 0.398399 0.076648 -0.290225
4 -1.375898 NaN -0.290225
5 0.932812 NaN -0.290225
In [47]: df.fillna(method='bfill') # 后向填充此时无效
Out[47]:
0 1 2
0 -0.858762 0.083342 -0.315598
1 -0.211846 0.076648 1.188298
2 -0.513364 NaN 0.079216
3 0.398399 NaN -0.290225
4 -1.375898 NaN NaN
5 0.932812 NaN NaN
其实使用fillna有很多技巧,需要大家平时多收集多尝试,比如使用平均值来填充:
In [48]: s = pd.Series([1, NA, 3.5, NA, 7])
In [49]: s.fillna(s.mean())
Out[49]:
0 1.000000
1 3.833333
2 3.500000
3 3.833333
4 7.000000
dtype: float64
删除重复值
原始数据中,往往包含大量重复的行,需要我们删除,让数据集更健康。
In [51]: df = pd.DataFrame({'k1':['one','two']*3 + ['two'], 'k2':[1,1,2,3,3,4,4]})
In [52]: df # 最后一行是重复的
Out[52]:
k1 k2
0 one 1
1 two 1
2 one 2
3 two 3
4 one 3
5 two 4
6 two 4
In [53]: df.duplicated() # 判断是否是重复行
Out[53]:
0 False
1 False
2 False
3 False
4 False
5 False
6 True
dtype: bool
In [54]: df.drop_duplicates() # 删除重复行
Out[54]:
k1 k2
0 one 1
1 two 1
2 one 2
3 two 3
4 one 3
5 two 4
In [55]: df # 并没有改变原数据
Out[55]:
k1 k2
0 one 1
1 two 1
2 one 2
3 two 3
4 one 3
5 two 4
6 two 4
上面,使用duplicated方法判断各行是否有重复,并返回一个布尔值Series。然后使用drop_duplicates方法将重复行删除,留下那些不重复的。
如果想指定根据某列的数据进行去重判断和操作,可以指定列名:
In [56]: df['v1'] = range(7)
In [57]: df
Out[57]:
k1 k2 v1
0 one 1 0
1 two 1 1
2 one 2 2
3 two 3 3
4 one 3 4
5 two 4 5
6 two 4 6
In [58]: df.drop_duplicates(['k1'])
Out[58]:
k1 k2 v1
0 one 1 0
1 two 1 1
默认情况下都是保留第一个观察到的值,如果想保留最后一个,可以使用参数keep='last':
In [59]: df.drop_duplicates(['k1','k2'], keep='last')
Out[59]:
k1 k2 v1
0 one 1 0
1 two 1 1
2 one 2 2
3 two 3 3
4 one 3 4
6 two 4 6
替换
可以使用replace将pandas对象中的指定值替换为别的值:
In [77]: df = pd.DataFrame(np.random.randint(12,size=(4,3)))
In [78]: df
Out[78]:
0 1 2
0 4 5 10
1 3 0 3
2 7 7 4
3 6 4 4
In [79]: df.replace(4, NA) # 将4替换为缺失值
Out[79]:
0 1 2
0 NaN 5.0 10.0
1 3.0 0.0 3.0
2 7.0 7.0 NaN
3 6.0 NaN NaN
In [80]: df.replace([3,4], NA) # 将3和4都替换为缺失值
Out[80]:
0 1 2
0 NaN 5.0 10.0
1 NaN 0.0 NaN
2 7.0 7.0 NaN
3 6.0 NaN NaN
In [81]: df.replace([3,4], [NA,0]) # 3和4分别替换为缺失值和0
Out[81]:
0 1 2
0 0.0 5.0 10.0
1 NaN 0.0 NaN
2 7.0 7.0 0.0
3 6.0 0.0 0.0
In [82]: df.replace({3:NA,4:0}) # 参数的字典形式
Out[82]:
0 1 2
0 0.0 5.0 10.0
1 NaN 0.0 NaN
2 7.0 7.0 0.0
3 6.0 0.0 0.0
重命名轴索引
与Series对象类似,轴索引也有一个map方法:
In [83]: df = pd.DataFrame(np.arange(12).reshape((3, 4)),
...: index=['Ohio', 'Colorado', 'New York'],
...: columns=['one', 'two', 'three', 'four'])
...:
In [84]: transform = lambda x: x[:4].upper() # 截取前4个字符并大写
In [85]: df.index.map(transform) # map的结果
Out[85]: Index(['OHIO', 'COLO', 'NEW '], dtype='object')
In [86]: df.index = df.index.map(transform) #用结果修改原来的index
In [87]: df
Out[87]:
one two three four
OHIO 0 1 2 3
COLO 4 5 6 7
NEW 8 9 10 11
还可以使用rename方法修改索引,且不修改原数据:
# 参数的值是对索引进行修改的处理函数,比如str.title
In [88]: df.rename(index=str.title, columns=str.upper)
Out[88]:
ONE TWO THREE FOUR
Ohio 0 1 2 3
Colo 4 5 6 7
New 8 9 10 11
In [89]: df # 原值未变
Out[89]:
one two three four
OHIO 0 1 2 3
COLO 4 5 6 7
NEW 8 9 10 11
或者使用字典的方式,将指定的索引重命名为新值:
In [90]: df.rename(index={'OHIO': 'INDIANA'},
...: columns={'three': 'peekaboo'})
...:
Out[90]:
one two peekaboo four
INDIANA 0 1 2 3
COLO 4 5 6 7
NEW 8 9 10 11
In [91]: df
Out[91]:
one two three four
OHIO 0 1 2 3
COLO 4 5 6 7
NEW 8 9 10 11
使用inplace=True可以原地修改数据集。
离散化和分箱
离散化,就是将连续值转换为一个个区间内,形成一个个分隔的‘箱子’。假设我们有下面的一群人的年龄数据,想将它们进行分组,并放入离散的年龄箱内:
ages = [20, 22, 25, 27, 21, 23, 37, 31, 61, 45, 41, 32]
我们预先定义1825、2635、36~60以及61及以上等若干组。
Pandas提供一个cut方法,帮助我们实现分箱功能:
In [93]: bins = [18,25,35,60,100]
In [94]: cats = pd.cut(ages,bins)
In [95]: cats
Out[95]:
[(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]
Length: 12
Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]
返回的cats是一个特殊的Categorical对象,输出描述了12个年龄值分别处于哪个箱子中。cats包含一系列的属性:
In [96]: cats.codes
Out[96]: array([0, 0, 0, 1, 0, 0, 2, 1, 3, 2, 2, 1], dtype=int8)
In [97]: cats.categories
Out[97]:
IntervalIndex([(18, 25], (25, 35], (35, 60], (60, 100]]
closed='right',
dtype='interval[int64]')
In [98]: cats.describe
Out[98]:
<bound method Categorical.describe of [(18, 25], (18, 25], (18, 25], (25, 35], (18, 25], ..., (25, 35], (60, 100], (35, 60], (35, 60], (25, 35]]
Length: 12
Categories (4, interval[int64]): [(18, 25] < (25, 35] < (35, 60] < (60, 100]]>
In [99]: pd.value_counts(cats) # 各个箱子的数量
Out[99]:
(18, 25] 5
(35, 60] 3
(25, 35] 3
(60, 100] 1
dtype: int64
分箱的区间通常是左开右闭的,如果想变成左闭右开,请设置参数right=False。
可以定义labels参数,来自定义每种箱子的名称:
In [100]: group_names = ['Youth', 'YoungAdult', 'MiddleAged', 'Senior']^M
...: pd.cut(ages, bins, labels=group_names)
...:
...:
Out[100]:
[Youth, Youth, Youth, YoungAdult, Youth, ..., YoungAdult, Senior, MiddleAged, MiddleAged, YoungAdult]
Length: 12
Categories (4, object): [Youth < YoungAdult < MiddleAged < Senior]
如果你不提供分箱的区间定义,而是直接要求分隔成整数个等分区间,可以这么做:
In [101]: d =np.random.rand(20)
In [102]: d
Out[102]:
array([0.83732945, 0.0850416 , 0.66540597, 0.90479238, 0.99222014,
0.39409122, 0.91896172, 0.87163655, 0.31374598, 0.27726111,
0.7716572 , 0.79131961, 0.42805445, 0.29934685, 0.19077374,
0.79701771, 0.93789892, 0.93536338, 0.32299602, 0.305671 ])
In [103]: pd.cut(d, 4, precision=2) # 精度限制在两位
Out[103]:
[(0.77, 0.99], (0.084, 0.31], (0.54, 0.77], (0.77, 0.99], (0.77, 0.99], ..., (0.77, 0.99], (0.77, 0.99], (0.77, 0.99], (0.31, 0.54], (0.084, 0.31]]
Length: 20
Categories (4, interval[float64]): [(0.084, 0.31] < (0.31, 0.54] < (0.54, 0.77] < (0.77, 0.99]]
cut函数执行的时候,分箱区间要么是你指定的,要么是均匀大小的。还有一种分箱方法叫做qcut,它是使用样本的分位数来分割的,而不是样本值的大小。比如下面的操作,将使每个箱子中元素的个数相等:
In [104]: data = np.random.randn(1000)
In [105]: cats = pd.qcut(data,4)
In [106]: cats
Out[106]:
[(0.644, 2.83], (-0.0344, 0.644], (-0.0344, 0.644], (-0.734, -0.0344], (-0.734, -0.0344], ..., (-3.327, -0.734], (-0.734, -0.0344], (0.644, 2.83], (-0.734, -0.0344], (-0.0344, 0.644]]
Length: 1000
Categories (4, interval[float64]): [(-3.327, -0.734] < (-0.734, -0.0344] < (-0.0344, 0.644] < (0.644, 2.83]]
In [108]: pd.value_counts(cats) # 各箱子中的元素个数相同
Out[108]:
(0.644, 2.83] 250
(-0.0344, 0.644] 250
(-0.734, -0.0344] 250
(-3.327, -0.734] 250
dtype: int64
qcut还可以自定义0~1之间的分位数:
pd.qcut(data, [0, 0.1, 0.5, 0.9, 1.])
离散化函数对于分位数和分组分析特别有用。
检测和过滤
检测和过滤异常值是数据清洗过程中非常重要的一步。比如你手里有个一万成人的身高数据,其中有几个3m以上,1m以下的数据,这都是属于异常值,需要被过滤掉。
我们来看下面的一组正态分布的数据:
In [109]: df = pd.DataFrame(np.random.randn(1000, 4))
In [110]: df.describe()
Out[110]:
0 1 2 3
count 1000.000000 1000.000000 1000.000000 1000.000000
mean -0.001764 0.023018 0.038956 0.016735
std 0.998835 1.041113 1.025342 1.019077
min -3.035805 -3.056706 -2.857154 -2.922755
25% -0.654426 -0.695994 -0.712627 -0.702766
50% -0.005015 0.011862 0.020562 -0.041231
75% 0.660182 0.683478 0.732590 0.758038
max 3.323073 3.701470 3.280375 3.997876
如果你想找出第二列数据中绝对值大于3的元素:
In [113]: col = df[2]
In [114]: col[np.abs(col) > 3]
Out[114]:
30 3.091161
113 3.280375
Name: 2, dtype: float64
如果要选出所有行内有值大于3或小于-3的行,可以使用下面的any方法搭配:
In [125]: df[(np.abs(df)>3).any(1)]
Out[125]:
0 1 2 3
28 0.008674 0.046048 -0.171580 3.997876
30 0.709758 -1.871982 3.091161 -0.819429
113 0.432223 -0.675313 3.280375 0.841355
169 3.323073 -0.608988 0.685795 -0.710693
177 -1.514524 -3.056706 -0.760937 1.300434
322 3.296765 0.971996 0.114804 1.855576
410 3.246140 -0.039501 1.530122 1.502243
496 -3.035805 -0.535662 0.703911 0.916483
575 -0.127245 3.701470 -0.642512 0.281001
720 3.045646 1.266809 1.263198 1.429049
799 0.523183 -0.246954 1.132868 3.141117
上面的代码怎么理解呢?首先,我们对整个df取绝对值,然后和3比较,形成一个bool的DataFrame,再使用any在行方向(参数1的作用)进行判断是否有True的存在,如果有,则保存在一个Series中,最后,用这个Series作为行号取df取出对应的行。
我们还可以将绝对值大于3的数分别设置为+3和-3,只需要使用np.sign(x)函数,这个函数根据x的符号分别生成+1和-1:
In [127]: df[np.abs(df) > 3] = np.sign(df) * 3
In [130]: df.describe()
Out[130]:
0 1 2 3
count 1000.000000 1000.000000 1000.000000 1000.000000
mean -0.002640 0.022374 0.038584 0.015596
std 0.995851 1.038699 1.024224 1.015232
min -3.000000 -3.000000 -2.857154 -2.922755
25% -0.654426 -0.695994 -0.712627 -0.702766
50% -0.005015 0.011862 0.020562 -0.041231
75% 0.660182 0.683478 0.732590 0.758038
max 3.000000 3.000000 3.000000 3.000000
In [133]: (np.sign(df)*3).head()
Out[133]:
0 1 2 3
0 3.0 3.0 -3.0 -3.0
1 3.0 3.0 -3.0 -3.0
2 -3.0 -3.0 -3.0 -3.0
3 -3.0 -3.0 3.0 -3.0
4 3.0 -3.0 3.0 -3.0
随机和抽样
有时候,我们需要打乱原有的数据顺序,让数据看起来像现实中比较混沌、自然的样子。这里推荐一个permutation操作,它来自numpy.random,可以随机生成一个序列:
In [134]: order = np.random.permutation(5) # 5个数
In [135]: order
Out[135]: array([3, 4, 1, 2, 0])
然后我们用它处理下面的df,让行的顺序变成和order的一样:
In [136]: df = pd.DataFrame(np.arange(5 * 4).reshape((5, 4)))
In [137]: df
Out[137]:
0 1 2 3
0 0 1 2 3
1 4 5 6 7
2 8 9 10 11
3 12 13 14 15
4 16 17 18 19
In [138]: df.take(order) #
Out[138]:
0 1 2 3
3 12 13 14 15
4 16 17 18 19
1 4 5 6 7
2 8 9 10 11
0 0 1 2 3
In [139]: df.iloc[order] # 同上
Out[139]:
0 1 2 3
3 12 13 14 15
4 16 17 18 19
1 4 5 6 7
2 8 9 10 11
0 0 1 2 3
可以看到,通过take函数,使用order作为参数,打乱了df的行顺序。
还有一种叫做抽样的操作,从原样本集合中抽取一部分形成新的样本集合,分重复抽样和不重复抽样。pandas提供的sample函数帮我们实现了这一功能:
In [140]: df.sample(n=3)
Out[140]:
0 1 2 3
0 0 1 2 3
4 16 17 18 19
1 4 5 6 7
In [141]: df.sample(n=10)
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
In [142]: df.sample(n=10,replace=True)
Out[142]:
0 1 2 3
3 12 13 14 15
2 8 9 10 11
2 8 9 10 11
0 0 1 2 3
3 12 13 14 15
0 0 1 2 3
2 8 9 10 11
4 16 17 18 19
1 4 5 6 7
3 12 13 14 15
- n=3:指定从原来数据中抽取3行
- n=10:弹出异常,因为原数据不够10行
- replace=True:可以重复抽取,这样10行可以,因为有重复
很明显,取样的操作是针对每行的。前面我们说过,一行就是一条记录,一个样本。
字符串操作
Python内置的字符串操作和re正则模块可以帮我们解决很多场景下的字符串操作需求。但是在数据分析过程中,它们有时候比较尴尬,比如:
In [143]: dic= {'one':'feixue', 'two':np.nan, 'three':'tom', 'five':'jerry@film'}
In [144]: s = pd.Series(dic)
In [145]: s
Out[145]:
one feixue
two NaN
three tom
five jerry@film
dtype: object
我现在想将s中的字母都大写,通过Python内置字符串方法,你可能会这么设计:
In [159]: s.map(lambda x : x.upper())
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
但是,弹出了异常,原因是数据有一个缺失值NaN,这个值不是字符串,没有upper方法。
那怎么办呢?Pandas为这一类整体性的操作,提供了专门的字符串函数,帮助我们跳过缺失值等异常情况,对能够进行操作的每个元素进行处理:
In [160]: s.str.upper()
Out[160]:
one FEIXUE
two NaN
three TOM
five JERRY@FILM
dtype: object
这就是Series的str属性,在它的基础上甚至可以使用正则表达式的函数。
下面是部分可用的Series.str的字符串操作方法,名字基本和Python字符串内置方法相同:
- cat :粘合字符串
- contains:是否包含的判断
- count:计数
- extract:返回匹配的字符串组
- endswith:以xx结尾判断
- startswith:以xx开始判断
- findall:查找
- get:获取
- isalnum:类型判断
- isalpha:类型判断
- isdecimal:类型判断
- isdigit:类型判断
- islower:是否小写
- isnumeric:类型判断
- isupper:是否大写
- join:连接
- len:长度
- lower:小写
- upper:大写
- match:匹配
- pad:将空白加到字符串的左边、右边或者两边
- center:居中
- repeat:重复
- replace:替换
- slice:切片
- split:分割
- strip:脱除
- lstrip:左脱除
- rstrip:右脱除
最后,思考一下,DataFrame怎么处理str呢?
分层索引
Pandas提供了Panel和Panel4D对象解决三维和四维数据的处理需求,但更常用的还是分层索引。分层索引是Pandas的重要特性,允许我们在一个轴向上拥有多个索引层级,它提供了一种在更低维度的形式中处理更高维度数据的方式。也就是如何用Series、DataFrame处理三维、四维等等高维度的数据。
这里还有一些 Pandas 的基本数据结构没有介绍到,包括 pd.Panel 对象和 pd.Panel4D 对象。这两种数据结构可以分别看成是(一维数组)Series 和(二维数组)DataFrame 的三维与四维形式。如果你熟悉Series 和 DataFrame 的使用方法,那么Panel 和 Panel4D 使用起来也会很简单,ix、loc 和 iloc 索引器在高维数据结构上的用法更是完全相同。
但是本书并不打算进一步介绍这两种数据结构,我个人认为多级索引在大多数情况下 都是更实用、更直观的高维数据形式。另外,Panel 采用密集数据存储形式,而多级 索引采用稀疏数据存储形式。在解决许多真实的数据集时,随着维度的不断增加,密 集数据存储形式的效率将越来越低。但是这类数据结构对一些有特殊需求的应用还是有用的。
比如有下面的数据:
In [168]: s = pd.Series(np.random.randn(9), index=[['a', 'a', 'a', 'b', 'b', 'c', 'c', 'd', 'd'], [1, 2, 3, 1, 3, 1, 2, 2, 3]])
In [169]: s
Out[169]:
a 1 0.283490
2 0.295529
3 0.277676
b 1 0.487573
3 0.091161
c 1 0.285157
2 -0.806851
d 2 -0.287969
3 -0.696511
dtype: float64
In [170]: s.index
Out[170]:
MultiIndex(levels=[['a', 'b', 'c', 'd'], [1, 2, 3]],
labels=[[0, 0, 0, 1, 1, 2, 2, 3, 3], [0, 1, 2, 0, 2, 0, 1, 1, 2]])
MultiIndex就是一个分层索引对象,在打印的时候会进行规整的美化。
下面看下一些基本的操作:
In [171]: s['b']
Out[171]:
1 0.487573
3 0.091161
dtype: float64
In [172]: s['b':'c']
Out[172]:
b 1 0.487573
3 0.091161
c 1 0.285157
2 -0.806851
dtype: float64
In [173]: s.loc[['b','d']]
Out[173]:
b 1 0.487573
3 0.091161
d 2 -0.287969
3 -0.696511
dtype: float64
In [174]: s.loc['b','d'] # 这样不可以
---------------------------------------------------------------------------
IndexingError Traceback (most recent call last)
IndexingError: Too many indexers
In [175]: s.loc['b',1] # 但是这样可以
Out[175]: 0.48757273896298425
In [176]: s.loc[:, 2] # 或者这样
Out[176]:
a 0.295529
c -0.806851
d -0.287969
dtype: float64
tup = []
我们还可以这样来生成MultiIndex,请体会它的不同之处:
In [3]: tup = [('beijing',2000),('beijing',2019),
...: ('shanghai',2000),('shanghai',2019),
...: ('guangzhou',2000),('guangzhou',2019)]
In [4]: values = [10000,100000,6000,60000,4000,40000]
In [7]: index = pd.MultiIndex.from_tuples(tup) # 利用元组生成MultiIndex
In [8]: sss = pd.Series(values, index=index) # 提供一个MultiIndex作为索引
In [9]: sss
Out[9]:
beijing 2000 10000
2019 100000
shanghai 2000 6000
2019 60000
guangzhou 2000 4000
2019 40000
dtype: int64
更多的创建MultiIndex的方法还有:
- 从列表:pd.MultiIndex.from_arrays([['a','a','b','b'],[1,2,1,2]])
- 从元组:pd.MultiIndex.from_tuples([('a',1),('a',2),('b',1),('b',2)])
- 笛卡儿积:pd.MultiIndex.from_product([['a','b'],[1,2]])
- 直接构造:pd.MultiIndex(levels=[['a','b'],[1,2]],labels=[[0,0,1,1],[0,1,0,1]])
生成MultiIndex对象后,就可以将这些对象作为参数,在创建Series或者DataFrame时传递给index。或者通过reindex方法更新已有的Series/DataFrame索引。
分层索引在重塑数据和数组透视表中非常重要。比如,我们可以使用unstack方法将数据在DataFrame中重新排列,也就是展开:
In [178]: s.unstack()
Out[178]:
1 2 3
a 0.283490 0.295529 0.277676
b 0.487573 NaN 0.091161
c 0.285157 -0.806851 NaN
d NaN -0.287969 -0.696511
In [179]: s.unstack().stack() # 反操作stack
Out[179]:
a 1 0.283490
2 0.295529
3 0.277676
b 1 0.487573
3 0.091161
c 1 0.285157
2 -0.806851
d 2 -0.287969
3 -0.696511
dtype: float64
对于DataFrame对象,每个轴都可以有分层索引,给index或columns提供一个多维数组,就可以分层:
In [3]: df = pd.DataFrame(np.arange(12).reshape((4, 3)),
...: index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
...: columns=[['Ohio', 'Ohio', 'Colorado'],
...: ['Green', 'Red', 'Green']])
In [4]: df
Out[4]:
Ohio Colorado
Green Red Green
a 1 0 1 2
2 3 4 5
b 1 6 7 8
2 9 10 11
可以给分层索引自定义名字:
In [5]: df.index.names
Out[5]: FrozenList([None, None])
In [6]: df.index.names = ['key1','key2']
In [7]: df.columns.names
Out[7]: FrozenList([None, None])
In [8]: df.columns.names = ['state','color']
In [9]: df
Out[9]:
state Ohio Colorado
color Green Red Green
key1 key2
a 1 0 1 2
2 3 4 5
b 1 6 7 8
2 9 10 11
In [10]: df['Ohio']
Out[10]:
color Green Red
key1 key2
a 1 0 1
2 3 4
b 1 6 7
2 9 10
1、 重排序和层级排序
有时候,我们需要重新排列轴上的层级顺序,或者按照特定的层级的值对数据进行排序。 Pandas的swaplevel方法用于这一功能,层级发生变更,但原数据不变。
In [11]: df.swaplevel('key1', 'key2')
Out[11]:
state Ohio Colorado
color Green Red Green
key2 key1
1 a 0 1 2
2 a 3 4 5
1 b 6 7 8
2 b 9 10 11
另外, sort_index
方法只能在单一层级上对数据进行排序。在进行层级变换的时候,使用sort_index
可以使得结果按照层级进行字典排序:
In [12]: df.sort_index(level=1)
Out[12]:
state Ohio Colorado
color Green Red Green
key1 key2
a 1 0 1 2
b 1 6 7 8
a 2 3 4 5
b 2 9 10 11
In [13]: df.swaplevel(0, 1).sort_index(level=0)
Out[13]:
state Ohio Colorado
color Green Red Green
key2 key1
1 a 0 1 2
b 6 7 8
2 a 3 4 5
b 9 10 11
sort_index(level=1)
意思是在第2个层级上进行索引的排序。
swaplevel(0, 1)
的意思是将第0层和第1层的行索引进行交换。
2、层级的汇总统计
使用level参数可以指定你想要在某个特定的轴上进行聚合。
In [15]: df.sum(level='key2')
Out[15]:
state Ohio Colorado
color Green Red Green
key2
1 6 8 10
2 12 14 16
In [16]: df.sum(level='color', axis=1)
Out[16]:
color Green Red
key1 key2
a 1 2 1
2 8 4
b 1 14 7
2 20 10
3、使用DataFrame的列进行索引
在DataFarme的索引操作中,还有一个比较特殊的场景,就是将一些列转换成层级行索引:
In [17]: 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]})
...:
In [18]: df
Out[18]:
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 [19]: df2 = df.set_index(['c','d'])
In [20]: df2
Out[20]:
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 [21]: df.set_index(['c','d'],drop=False)
Out[21]:
a b c d
c d
one 0 0 7 one 0
1 1 6 one 1
2 2 5 one 2
two 0 3 4 two 0
1 4 3 two 1
2 5 2 two 2
3 6 1 two 3
In [22]: df2.reset_index()
Out[22]:
c d a b
0 one 0 0 7
1 one 1 1 6
2 one 2 2 5
3 two 0 3 4
4 two 1 4 3
5 two 2 5 2
6 two 3 6 1
set_index(['c','d'])
,将c列和d列变成了分层的行索引drop=False
则保留了原来的列数据reset_index
是set_index
的反向操作
4、分层索引的取值与切片
先看针对Series的操作:
In [3]: tup = [('beijing',2000),('beijing',2019),
...: ('shanghai',2000),('shanghai',2019),
...: ('guangzhou',2000),('guangzhou',2019)]
In [4]: values = [10000,100000,6000,60000,4000,40000]
In [7]: index = pd.MultiIndex.from_tuples(tup)
In [8]: s = pd.Series(values, index=index)
In [9]: s
Out[9]:
beijing 2000 10000
2019 100000
shanghai 2000 6000
2019 60000
guangzhou 2000 4000
2019 40000
dtype: int64
In [10]: s['beijing',2019] #获取单个元素
Out[10]: 100000
In [11]: s['shanghai'] #局部取值得到一个新的Series
Out[11]:
2000 6000
2019 60000
dtype: int64
In [15]: s[:,2000] #空切片取值
Out[15]:
beijing 10000
shanghai 6000
guangzhou 4000
dtype: int64
In [17]: s[s>5000] #布尔掩码取值
Out[17]:
beijing 2000 10000
2019 100000
shanghai 2000 6000
2019 60000
guangzhou 2019 40000
dtype: int64
In [18]: s[['shanghai','guangzhou']] #花式索引取值
Out[18]:
shanghai 2000 6000
2019 60000
guangzhou 2000 4000
2019 40000
dtype: int64
再看看DataFrame,需要记住和理解的核心是:
- 这是一个分层索引DataFrame对象,不是单级的
- 默认以列为操作对象
In [19]: df = pd.DataFrame(np.arange(12).reshape((4, 3)),
...: index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
...: columns=[['Ohio', 'Ohio', 'Colorado'],
...: ['Green', 'Red', 'Green']])
...:
In [20]: df
Out[20]:
Ohio Colorado
Green Red Green
a 1 0 1 2
2 3 4 5
b 1 6 7 8
2 9 10 11
In [23]: df['Ohio','Colorado'] # 不能这么做,因为列索引分层了
---------------------------------------------------------------------------
KeyError Traceback (most recent call last)
In [24]: df[['Ohio','Colorado']] # 这样可以
Out[24]:
Ohio Colorado
Green Red Green
a 1 0 1 2
2 3 4 5
b 1 6 7 8
2 9 10 11
In [25]: df['Ohio','Green'] # 每层提供一个参数
Out[25]:
a 1 0
2 3
b 1 6
2 9
Name: (Ohio, Green), dtype: int32
In [26]: df.iloc[:2,:2] # 用隐式索引
Out[26]:
Ohio
Green Red
a 1 0 1
2 3 4
In [28]: df.loc[:,('Ohio','Red')] # 这个比较难理解
Out[28]:
a 1 1
2 4
b 1 7
2 10
Name: (Ohio, Red), dtype: int32
另外最后要提醒的是:如果MultiIndex不是有序的索引,那么大多数切片操作都会失败!这时候可以使用前面介绍过的sort_index
方法先排下序。
合并连接
可以通过多种方式将Pandas对象联合到一起:
- pandas.merge: 根据一个或多个键进行连接。类似SQL的连接操作
- pandas.concat:使对象在轴向上进行粘合或者‘堆叠’
- combine_first:将重叠的数据拼接在一起,使用一个对象中的值填充另一个对象中的缺失值
1、merge连接
merge方法将两个pandas对象连接在一起,类似SQL的连接操作。默认情况下,它执行的是内连接,也就是两个对象的交集。通过参数how,还可以指定外连接、左连接和右连接。参数on指定在哪个键上连接,参数left_on
和right_on
分别指定左右对象的连接键。
- 外连接:并集
- 内连接:交集
- 左连接:左边对象全部保留
- 右连接:右边对象全部保留
In [23]: df1 = pd.DataFrame({'key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
...: 'data1': range(7)})
...:
In [24]: df2 = pd.DataFrame({'key': ['a', 'b', 'd'],
...: 'data2': range(3)})
...:
In [25]: df1
Out[25]:
key data1
0 b 0
1 b 1
2 a 2
3 c 3
4 a 4
5 a 5
6 b 6
In [26]: df2
Out[26]:
key data2
0 a 0
1 b 1
2 d 2
In [27]: pd.merge(df1,df2) # 默认内链接,并智能地查找连接的键
Out[27]:
key data1 data2
0 b 0 1
1 b 1 1
2 b 6 1
3 a 2 0
4 a 4 0
5 a 5 0
In [28]: pd.merge(df1,df2,on='key') # 最好是显式地指定连接的键
Out[28]:
key data1 data2
0 b 0 1
1 b 1 1
2 b 6 1
3 a 2 0
4 a 4 0
5 a 5 0
In [30]: pd.merge(df1, df2, how='outer') # 外连接
Out[30]:
key data1 data2
0 b 0.0 1.0
1 b 1.0 1.0
2 b 6.0 1.0
3 a 2.0 0.0
4 a 4.0 0.0
5 a 5.0 0.0
6 c 3.0 NaN
7 d NaN 2.0
In [31]: pd.merge(df1, df2, how='left') # 左连接
Out[31]:
key data1 data2
0 b 0 1.0
1 b 1 1.0
2 a 2 0.0
3 c 3 NaN
4 a 4 0.0
5 a 5 0.0
6 b 6 1.0
In [32]: pd.merge(df1, df2, how='right') #右连接
Out[32]:
key data1 data2
0 b 0.0 1
1 b 1.0 1
2 b 6.0 1
3 a 2.0 0
4 a 4.0 0
5 a 5.0 0
6 d NaN 2
In [33]: df3 = pd.DataFrame({'lkey': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
...: 'data1': range(7)})
...:
In [34]: df4 = pd.DataFrame({'rkey': ['a', 'b', 'd'],
...: 'data2': range(3)})
...:
In [35]: pd.merge(df3, df4, left_on='lkey', right_on='rkey') # 指定两边的键
Out[35]:
lkey data1 rkey data2
0 b 0 b 1
1 b 1 b 1
2 b 6 b 1
3 a 2 a 0
4 a 4 a 0
5 a 5 a 0
多对多的merge连接是行的笛卡儿积。比如左边如果有3个‘b’行,右边有2个‘b’行,那么结果是3x2,6个‘b’行。
也可以同时指定多个键进行连接:
In [36]: left = pd.DataFrame({'key1': ['foo', 'foo', 'bar'],
...: 'key2': ['one', 'two', 'one'],
...: 'lval': [1, 2, 3]})
In [37]: right = pd.DataFrame({'key1': ['foo', 'foo', 'bar', 'bar'],
...: 'key2': ['one', 'one', 'one', 'two'],
...: 'rval': [4, 5, 6, 7]})
In [38]: pd.merge(left, right, on=['key1', 'key2'], how='outer')
Out[38]:
key1 key2 lval rval
0 foo one 1.0 4.0
1 foo one 1.0 5.0
2 foo two 2.0 NaN
3 bar one 3.0 6.0
4 bar two NaN 7.0
merge操作中还有一个重叠列名的问题,比如上面的left和right两个数据,为此,我们可以使用suffixes参数,手动指定为重复的列名添加后缀:
In [41]: pd.merge(left, right, on='key1')
Out[41]:
key1 key2_x lval key2_y rval
0 foo one 1 one 4
1 foo one 1 one 5
2 foo two 2 one 4
3 foo two 2 one 5
4 bar one 3 one 6
5 bar one 3 two 7
In [42]: pd.merge(left, right, on='key1', suffixes=('_left', '_right'))
Out[42]:
key1 key2_left lval key2_right rval
0 foo one 1 one 4
1 foo one 1 one 5
2 foo two 2 one 4
3 foo two 2 one 5
4 bar one 3 one 6
5 bar one 3 two 7
有时候,用于merge合并的键可能是某个对象的行索引:
In [43]: left1 = pd.DataFrame({'key': ['a', 'b', 'a', 'a', 'b', 'c'],
...: 'value': range(6)})
...:
In [44]: right1 = pd.DataFrame({'group_val': [3.5, 7]}, index=['a', 'b'])
In [45]: left1
Out[45]:
key value
0 a 0
1 b 1
2 a 2
3 a 3
4 b 4
5 c 5
In [46]: right1
Out[46]:
group_val
a 3.5
b 7.0
In [47]: pd.merge(left1, right1, left_on='key', right_index=True)
Out[47]:
key value group_val
0 a 0 3.5
2 a 2 3.5
3 a 3 3.5
1 b 1 7.0
4 b 4 7.0
In [48]: pd.merge(left1, right1, left_on='key', right_index=True, how='outer')
Out[48]:
key value group_val
0 a 0 3.5
2 a 2 3.5
3 a 3 3.5
1 b 1 7.0
4 b 4 7.0
5 c 5 NaN
使用right_index=True
参数显式地指出,右边的对象right1使用它的行索引作为连接的键。
事实上Pandas有一个join方法,可以帮助我们直接用行索引进行连接:
In [49]: left2 = pd.DataFrame([[1., 2.], [3., 4.], [5., 6.]],
...: index=['a', 'c', 'e'],
...: columns=['Ohio', 'Nevada'])
...:
In [50]: right2 = pd.DataFrame([[7., 8.], [9., 10.], [11., 12.], [13, 14]],
...: index=['b', 'c', 'd', 'e'],
...: columns=['Missouri', 'Alabama'])
...:
In [51]: left2
Out[51]:
Ohio Nevada
a 1.0 2.0
c 3.0 4.0
e 5.0 6.0
In [52]: right2
Out[52]:
Missouri Alabama
b 7.0 8.0
c 9.0 10.0
d 11.0 12.0
e 13.0 14.0
In [53]: pd.merge(left2, right2, how='outer', left_index=True, right_index=True)
Out[53]:
Ohio Nevada Missouri Alabama
a 1.0 2.0 NaN NaN
b NaN NaN 7.0 8.0
c 3.0 4.0 9.0 10.0
d NaN NaN 11.0 12.0
e 5.0 6.0 13.0 14.0
In [54]: left2.join(right2, how='outer') # 与上面的操作效果一样
Out[54]:
Ohio Nevada Missouri Alabama
a 1.0 2.0 NaN NaN
b NaN NaN 7.0 8.0
c 3.0 4.0 9.0 10.0
d NaN NaN 11.0 12.0
e 5.0 6.0 13.0 14.0
2、轴向连接
concat方法可以实现对象在轴向的的粘合或者堆叠。
In [55]: s1 = pd.Series([0, 1], index=['a', 'b'])
In [56]: s2 = pd.Series([2, 3, 4], index=['c', 'd', 'e'])
In [57]: s3 = pd.Series([5, 6], index=['f', 'g'])
In [58]: pd.concat([s1, s2, s3]) # 要以列表的方式提供参数
Out[58]:
a 0
b 1
c 2
d 3
e 4
f 5
g 6
dtype: int64
In [59]: pd.concat([s1, s2, s3], axis=1) # 横向堆叠,但出现警告信息
C:\ProgramData\Anaconda3\Scripts\ipython:1: FutureWarning: Sorting because non-concatenation axis is not aligned. A future version
of pandas will change to not sort by default.
......
Out[59]:
0 1 2
a 0.0 NaN NaN
b 1.0 NaN NaN
c NaN 2.0 NaN
d NaN 3.0 NaN
e NaN 4.0 NaN
f NaN NaN 5.0
g NaN NaN 6.0
In [60]: pd.concat([s1, s2, s3], axis=1,sort=True) # 按人家的要求做
Out[60]:
0 1 2
a 0.0 NaN NaN
b 1.0 NaN NaN
c NaN 2.0 NaNDIP_GROUP
d NaN 3.0 NaN
e NaN 4.0 NaN
f NaN NaN 5.0
g NaN NaN 6.0
对于DataFrame,默认情况下都是按行往下合并的,当然也可以设置axis参数:
In [66]: df1 = pd.DataFrame(np.arange(6).reshape(3, 2), index=['a', 'b', 'c'],
...: columns=['one', 'two'])
...:
In [67]: df2 = pd.DataFrame(5 + np.arange(4).reshape(2, 2), index=['a', 'c'],
...: columns=['three', 'four'])
...:
In [68]: df1
Out[68]:
one two
a 0 1
b 2 3
c 4 5
In [69]: df2
Out[69]:
three four
a 5 6
c 7 8
In [71]: pd.concat([df1, df2], sort=True)
Out[71]:
four one three two
a NaN 0.0 NaN 1.0
b NaN 2.0 NaN 3.0
c NaN 4.0 NaN 5.0
a 6.0 NaN 5.0 NaN
c 8.0 NaN 7.0 NaN
In [72]: pd.concat([df1, df2], axis=1, sort=True)
Out[72]:
one two three four
a 0 1 5.0 6.0
b 2 3 NaN NaN
c 4 5 7.0 8.0
append()方法 因为直接进行数组合并的需求非常普遍,所以 Series 和 DataFrame 对象都支持 append 方 法,让你通过最少的代码实现合并功能。例如,你可以使用 df1.append(df2),效果与 pd.concat([df1, df2]) 一样:
In[16]: print(df1);
print(df2);
print(df1.append(df2))
df1 df2 df1.append(df2)
A B A B A B
1 A1 B1 3 A3 B3 1 A1 B1
2 A2 B2 4 A4 B4 2 A2 B2
3 A3 B3
4 A4 B4
需要注意的是,与 Python 列表中的 append() 和 extend() 方法不同,Pandas 的 append() 不直接更新原有对象的值,而是为合并后的数据创建一个新对象。因此,它不能被称之为一 个非常高效的解决方案,因为每次合并都需要重新创建索引和数据缓存。总之,如果你需 要进行多个 append 操作,还是建议先创建一个 DataFrame 列表,然后用 concat() 函数一次 性解决所有合并任务。
3、联合叠加
有这么种场景,某个对象里缺失的值,拿另外一个对象的相应位置的值来填补。在Numpy层面,可以这么做:
In [74]: a = pd.Series([np.nan, 2.5, 0, 3.5, 4.5, np.nan],
...: index=['f', 'e', 'd', 'c', 'b', 'a'])
In [75]: b = pd.Series([0, np.nan, 2.1, np.nan, np.nan, 5], index=list('abcdef'))
In [76]: a
Out[76]:
f NaN
e 2.5
d 0.0
c 3.5
b 4.5
a NaN
dtype: float64
In [77]: b
Out[77]:
a 0.0
b NaN
c 2.1
d NaN
e NaN
f 5.0
dtype: float64
In [78]: np.where(pd.isnull(a), b, a)
Out[78]: array([0. , 2.5, 0. , 3.5, 4.5, 5. ])
np.where(pd.isnull(a), b, a)
,这一句里,首先去pd.isnull(a)
种判断元素,如果是True,从b里拿数据,否则从a里拿,得到最终结果。
实际上,Pandas为这种场景提供了一个专门的combine_first
方法:
In [80]: b.combine_first(a)
Out[80]:
a 0.0
b 4.5
c 2.1
d 0.0
e 2.5
f 5.0
dtype: float64
对于DataFrame对象,combine_first
逐列做相同的操作,因此你可以认为它是根据你传入的对象来‘修补’调用对象的缺失值。
In [81]: df1 = pd.DataFrame({'a': [1., np.nan, 5., np.nan],
...: 'b': [np.nan, 2., np.nan, 6.],
...: 'c': range(2, 18, 4)})
...:
In [82]: df2 = pd.DataFrame({'a': [5., 4., np.nan, 3., 7.],
...: 'b': [np.nan, 3., 4., 6., 8.]})
...:
In [83]: df1
Out[83]:
a b c
0 1.0 NaN 2
1 NaN 2.0 6
2 5.0 NaN 10
3 NaN 6.0 14
In [84]: df2
Out[84]:
a b
0 5.0 NaN
1 4.0 3.0
2 NaN 4.0
3 3.0 6.0
4 7.0 8.0
In [85]: df1.combine_first(df2)
Out[85]:
a b c
0 1.0 NaN 2.0
1 4.0 2.0 6.0
2 5.0 4.0 10.0
3 3.0 6.0 14.0
4 7.0 8.0 NaN
重塑
对表格型数据进行重新排列的操作,被称作重塑或者透视。
使用多层索引进行重塑主要有stack和unstack操作,前面有介绍过。
In [93]: df = pd.DataFrame(np.arange(6).reshape(2,3),
...: index=pd.Index(['河南','山西'], name='省份'),
...: columns=pd.Index(['one','two','three'],name='number'))
In [94]: df
Out[94]:
number one two three
省份
河南 0 1 2
山西 3 4 5
In [95]: result = df.stack()
In [96]: result
Out[96]:
省份 number
河南 one 0
two 1
three 2
山西 one 3
two 4
three 5
dtype: int32
In [97]: result.unstack()
Out[97]:
number one two three
省份
河南 0 1 2
山西 3 4 5
stack操作使得df的所有列都变成了分层行索引,产生了一个新的Series。
unstack默认情况下拆分最内层索引,然后将数据放入一个DataFrame中。可以传入一个层级序号或名称来拆分不同的层级。
In [98]: result.unstack(0)
Out[98]:
省份 河南 山西
number
one 0 3
two 1 4
three 2 5
In [99]: result.unstack('省份')
Out[99]:
省份 河南 山西
number
one 0 3
two 1 4
three 2 5
如果层级中的所有值并未有包含于每个子分组中,拆分可能会导致缺失值的产生:
In [100]: s1 = pd.Series([0, 1, 2, 3], index=['a', 'b', 'c', 'd'])
In [101]: s2 = pd.Series([4, 5, 6], index=['c', 'd', 'e'])
In [102]: data2 = pd.concat([s1, s2], keys=['one', 'two'])
In [103]: data2 # 注意concat的结果是一个分层索引
Out[103]:
one a 0
b 1
c 2
d 3
two c 4
d 5
e 6
dtype: int64
In [104]: data2.unstack()
Out[104]:
a b c d e
one 0.0 1.0 2.0 3.0 NaN
two NaN NaN 4.0 5.0 6.0
In [105]: data2.unstack().stack() # 结果是可逆的
Out[105]:
one a 0.0
b 1.0
c 2.0
d 3.0
two c 4.0
d 5.0
e 6.0
dtype: float64
In [106]: data2.unstack().stack(dropna=False) # 保留缺失值
Out[106]:
one a 0.0
b 1.0
c 2.0
d 3.0
e NaN
two a NaN
b NaN
c 4.0
d 5.0
e 6.0
dtype: float64
而在DataFrame对象拆堆时,被拆的层级会变成结果中最低的层级:
In [107]: df = pd.DataFrame({'left': result, 'right': result + 5},
...: columns=pd.Index(['left', 'right'], name='side'))
...:
In [108]: df
Out[108]:
side left right
省份 number
河南 one 0 5
two 1 6
three 2 7
山西 one 3 8
two 4 9
three 5 10
In [109]: df.unstack('省份') # 因为作死引入了中文,所以版式不太对齐
Out[109]:
side left right
省份 河南 山西 河南 山西
number
one 0 3 5 8
two 1 4 6 9
three 2 5 7 10
GroupBy:分割、应用和组合
我们经常还需要对某些标签或索引的局部进行累计分析,这时就需要用到 groupby 了。虽然“分组”(group by)这个名字是借用 SQL 数据库语言的命令,但其理念引用发明 R 语言 frame 的 Hadley Wickham 的观点可能更合适:分割(split)、 应用(apply)和组合(combine)。
1、分割、应用和组合
一个经典分割 - 应用 - 组合操作示例如图所示:
过程:
- 分割步骤将 DataFrame 按照指定的键分割成若干组。
- 应用步骤对每个组应用函数,通常是累计、转换或过滤函数。
- 组合步骤将每一组的结果合并成一个输出数组。
虽然我们也可以通过前面介绍的一系列的掩码、累计与合并操作来实现,但是意识到中间分割过程不需要显式地暴露出来这一点十分重要。而且 GroupBy(经常)只需要一行代码, 就可以计算每组的和、均值、计数、最小值以及其他累计值。GroupBy 的用处就是将这些步骤进行抽象:用户不需要知道在底层如何计算,只要把操作看成一个整体就够了。
用 Pandas 进行图所示的计算作为具体的示例。从创建输入 DataFrame 开始:
In[11]: df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'], 'data': range(6)}, columns=['key', 'data'])
df
Out[11]: key data
0 A 0
1 B 1
2 C 2
3 A 3
4 B 4
5 C 5
我们可以用 DataFrame 的 groupby() 方法进行绝大多数常见的分割 - 应用 - 组合操作,将 需要分组的列名传进去即可:
In[12]: df.groupby('key')
Out[12]: <pandas.core.groupby.DataFrameGroupBy object at 0x117272160>
需要注意的是,这里的返回值不是一个 DataFrame 对象,而是一个 DataFrameGroupBy 对象。 这个对象的魔力在于,你可以将它看成是一种特殊形式的 DataFrame,里面隐藏着若干组 数据,但是在没有应用累计函数之前不会计算。这种“延迟计算”(lazy evaluation)的方法使得大多数常见的累计操作可以通过一种对用户而言几乎是透明的(感觉操作仿佛不存在)方式非常高效地实现。
为了得到这个结果,可以对 DataFrameGroupBy 对象应用累计函数,它会完成相应的应用 / 组合步骤并生成结果:
In[13]: df.groupby('key').sum()
Out[13]: data
key
A 3
B 5
C 7
sum() 只是众多可用方法中的一个。你可以用 Pandas 或 NumPy 的任意一种累计函数,也可以用任意有效的 DataFrame 对象。
2、GroupBy对象
GroupBy 对象是一种非常灵活的抽象类型。在大多数场景中,你可以将它看成是 DataFrame 的集合,在底层解决所有难题。让我们用行星数据来做一些演示。
GroupBy 中最重要的操作可能就是 aggregate、filter、transform 和 apply(累计、过滤、转换、应用)了,现在先来介绍一些 GroupBy 的基本操作方法。
(1) 按列取值
GroupBy 对象与 DataFrame 一样,也支持按列取值,并返回一个修改过的 GroupBy 对象,例如:
In[14]: planets.groupby('method')
Out[14]: <pandas.core.groupby.DataFrameGroupBy object at 0x1172727b8>
In[15]: planets.groupby('method')['orbital_period']
Out[15]: <pandas.core.groupby.SeriesGroupBy object at 0x117272da0>
这里从原来的 DataFrame 中取某个列名作为一个 Series 组。与 GroupBy 对象一样,直到我们运行累计函数,才会开始计算:
In[16]: planets.groupby('method')['orbital_period'].median()
Out[16]: method
Astrometry 631.180000
Eclipse Timing Variations 4343.500000
Imaging 27500.000000
Microlensing 3300.000000
Orbital Brightness Modulation 0.342887
Pulsar Timing 66.541900
Pulsation Timing Variations 1170.000000
Radial Velocity 360.200000
Transit 5.714932
Transit Timing Variations 57.011000
Name: orbital_period, dtype: float64
这样就可以获得不同方法下所有行星公转周期(按天计算)的中位数。
(2) 按组迭代
GroupBy 对象支持直接按组进行迭代,返回的每一组都是 Series 或 DataFrame:
In[17]: for (method, group) in planets.groupby('method'):
print("{0:30s} shape={1}".format(method, group.shape))
Astrometry shape=(2, 6)
Eclipse Timing Variations shape=(9, 6)
Imaging shape=(38, 6)
Microlensing shape=(23, 6)
Orbital Brightness Modulation shape=(3, 6)
Pulsar Timing shape=(5, 6)
Pulsation Timing Variations shape=(1, 6)
Radial Velocity shape=(553, 6)
Transit shape=(397, 6)
Transit Timing Variations shape=(4, 6)
尽管通常还是使用内置的 apply 功能速度更快,但这种方式在手动处理某些问题时非常有用。
(3) 调用方法
借助 Python 类的魔力(@classmethod),可以让任何不由 GroupBy 对象直接实现的方法直接应用到每一组,无论是 DataFrame 还是 Series 对象都同样适用。例如, 你可以用 DataFrame 的 describe() 方法进行累计,对每一组数据进行描述性统计:
In[18]: planets.groupby('method')['year'].describe().unstack()
Out[18]:
count mean std min 25% \\
method
Astrometry 2.0 2011.500000 2.121320 2010.0 2010.75
Eclipse Timing Variations 9.0 2010.000000 1.414214 2008.0 2009.00
Imaging 38.0 2009.131579 2.781901 2004.0 2008.00
Microlensing 23.0 2009.782609 2.859697 2004.0 2008.00
Orbital Brightness Modulation 3.0 2011.666667 1.154701 2011.0 2011.00
Pulsar Timing 5.0 1998.400000 8.384510 1992.0 1992.00
Pulsation Timing Variations 1.0 2007.000000 NaN 2007.0 2007.00
Radial Velocity 553.0 2007.518987 4.249052 1989.0 2005.00
Transit 397.0 2011.236776 2.077867 2002.0 2010.00
Transit Timing Variations 4.0 2012.500000 1.290994 2011.0 2011.75
50% 75% max
method
Astrometry 2011.5 2012.25 2013.0
Eclipse Timing Variations 2010.0 2011.00 2012.0
Imaging 2009.0 2011.00 2013.0
Microlensing 2010.0 2012.00 2013.0
Orbital Brightness Modulation 2011.0 2012.00 2013.0
Pulsar Timing 1994.0 2003.00 2011.0
Pulsation Timing Variations 2007.0 2007.00 2007.0
Radial Velocity 2009.0 2011.00 2014.0
Transit 2012.0 2013.00 2014.0
Transit Timing Variations 2012.5 2013.25 2014.0
这张表可以帮助我们对数据有更深刻的认识,例如大多数行星都是通过 Radial Velocity 和 Transit 方法发现的,而且后者在近十年变得越来越普遍(得益于更新、更精确的望远镜)。最新的 Transit Timing Variation 和 Orbital Brightness Modulation 方法在 2011 年之后才有新的发现。
这只是演示Pandas 调用方法的示例之一。方法首先会应用到每组数据上,然后结果由 GroupBy 组合后返回。另外,任意 DataFrame / Series 的方法都可以由 GroupBy 方法调用, 从而实现非常灵活强大的操作。
3、累计、过滤、转换和应用
虽然前面的章节只重点介绍了组合操作,但是还有许多操作没有介绍,尤其是 GroupBy 对象的 aggregate()、filter()、transform() 和 apply() 方法,在数据组合之前实现了大量高效的操作。
为了方便后面内容的演示,使用下面这个 DataFrame:
In[19]: rng = np.random.RandomState(0)
df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'],
'data1': range(6),
'data2': rng.randint(0, 10, 6)},
columns = ['key', 'data1', 'data2'])
df
Out[19]: key data1 data2
0 A 0 5
1 B 1 0
2 C 2 3
3 A 3 3
4 B 4 7
5 C 5 9
(1) 累计
我们目前比较熟悉的 GroupBy 累计方法只有 sum() 和 median() 之类的简单函数, 但是 aggregate() 其实可以支持更复杂的操作,比如字符串、函数或者函数列表,并且能一次性计算所有累计值。下面来快速演示一个例子:
In[20]: df.groupby('key').aggregate(['min', np.median, max])
Out[20]: data1 data2
min median max min median max
key
A 0 1.5 3 3 4.0 5
B 1 2.5 4 0 3.5 7
C 2 3.5 5 3 6.0 9
另一种用法就是通过 Python 字典指定不同列需要累计的函数:
In[21]: df.groupby('key').aggregate({'data1': 'min', 'data2': 'max'})
Out[21]: data1 data2
key
A 0 5
B 1 7
C 2 9
(2) 过滤
过滤操作可以让你按照分组的属性丢弃若干数据。例如,我们可能只需要保留标准差超过某个阈值的组:
In[22]:
def filter_func(x):
return x['data2'].std() > 4
print(df); print(df.groupby('key').std());
print(df.groupby('key').filter(filter_func))
df df.groupby('key').std()
key data1 data2 key data1 data2
0 A 0 5 A 2.12132 1.414214
1 B 1 0 B 2.12132 4.949747
2 C 2 3 C 2.12132 4.242641
3 A 3 3
4 B 4 7
5 C 5 9
df.groupby('key').filter(filter_func)
key data1 data2
1 B 1 0
2 C 2 3
4 B 4 7
5 C 5 9
filter() 函数会返回一个布尔值,表示每个组是否通过过滤。由于 A 组 'data2' 列的 标准差不大于 4,所以被丢弃了。
(3) 转换
累计操作返回的是对组内全量数据缩减过的结果,而转换操作会返回一个新的全量数据。数据经过转换之后,其形状与原来的输入数据是一样的。常见的例子就是将每 一组的样本数据减去各组的均值,实现数据标准化:
In[23]: df.groupby('key').transform(lambda x: x - x.mean())
Out[23]: data1 data2
0 -1.5 1.0
1 -1.5 -3.5
2 -1.5 -3.0
3 1.5 -1.0
4 1.5 3.5
5 1.5 3.0
(4) apply() 方法
apply() 方法让你可以在每个组上应用任意方法。这个函数输入一个 DataFrame,返回一个 Pandas 对象(DataFrame 或 Series)或一个标量(scalar,单个数 值)。组合操作会适应返回结果类型。
下面的例子就是用 apply() 方法将第一列数据以第二列的和为基数进行标准化:
In[24]: def norm_by_data2(x):
# x是一个分组数据的DataFrame
x['data1'] /= x['data2'].sum()
return x
print(df); print(df.groupby('key').apply(norm_by_data2))
df df.groupby('key').apply(norm_by_data2)
key data1 data2 key data1 data2
0 A 0 5 0 A 0.000000 5
1 B 1 0 1 B 0.142857 0
2 C 2 3 2 C 0.166667 3
3 A 3 3 3 A 0.375000 3
4 B 4 7 4 B 0.571429 7
5 C 5 9 5 C 0.416667 9
GroupBy 里的apply() 方法非常灵活,唯一需要注意的地方是它总是输入分组数据的 DataFrame,返回 Pandas 对象或标量。具体如何选择需要视情况而定。
4、设置分割的键
前面的简单例子一直在用列名分割 DataFrame。这只是众多分组操作中的一种,下面将继续介绍更多的分组方法。
(1) 将列表、数组、Series 或索引作为分组键
分组键可以是长度与 DataFrame 匹配的任意 Series 或列表,例如:
In[25]: L = [0, 1, 0, 1, 2, 0]
print(df); print(df.groupby(L).sum())
df df.groupby(L).sum()
key data1 data2 data1 data2
0 A 0 5 0 7 17
1 B 1 0 1 4 3
2 C 2 3 2 4 7
3 A 3 3
4 B 4 7
5 C 5 9
因此,还有一种比前面直接用列名更啰嗦的表示方法 df.groupby('key'):
In[26]: print(df); print(df.groupby(df['key']).sum())
df df.groupby(df['key']).sum()
key data1 data2 data1 data2
0 A 0 5 A 3 8
1 B 1 0 B 5 7
2 C 2 3 C 7 12
3 A 3 3
4 B 4 7
5 C 5 9
(2) 用字典或 Series 将索引映射到分组名称
另一种方法是提供一个字典,将索引映射到分组键:
In[27]: df2 = df.set_index('key')
mapping = {'A': 'vowel', 'B': 'consonant', 'C': 'consonant'}
print(df2); print(df2.groupby(mapping).sum())
df2 df2.groupby(mapping).sum()
key data1 data2 data1 data2
A 0 5 consonant 12 19
B 1 0 vowel 3 8
C 2 3
A 3 3
B 4 7
C 5 9
(3) 任意Python 函数
与前面的字典映射类似,你可以将任意 Python 函数传入 groupby, 函数映射到索引,然后新的分组输出:
In[28]: print(df2); print(df2.groupby(str.lower).mean())
df2 df2.groupby(str.lower).mean()
key data1 data2 data1 data2
A 0 5 a 1.5 4.0
B 1 0 b 2.5 3.5
C 2 3 c 3.5 6.0
A 3 3
B 4 7
C 5 9
(4) 多个有效键构成的列表
此外,任意之前有效的键都可以组合起来进行分组,从而返回 一个多级索引的分组结果:
In[29]: df2.groupby([str.lower, mapping]).mean()
Out[29]: data1 data2
a vowel 1.5 4.0
b consonant 2.5 3.5
c consonant 3.5 6.0
标签:df,dtype,NaN,DataFrame,pd,Pandas,Out
From: https://www.cnblogs.com/simpleness/p/17624445.html