欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Python for Data Analysis v2 | Notes_ Chapter_5 pandas 入门

程序员文章站 2022-07-14 21:45:12
...

本人以简书作者 SeanCheney 系列专题文章并结合原书为学习资源,记录个人笔记,仅作为知识记录及后期复习所用,原作者地址查看 简书 SeanCheney,如有错误,还望批评指教。——ZJ

原作者:SeanCheney | 利用 Python 进行数据分析·第2版》第5章 pandas 入门 | 來源:简书

Github:wesm | Github:中文 BrambleXu|
简书:利用 Python 进行数据分析·第2版

环境: Python 3.6


第 5 章 pandas 入门

  • pandas 是本书后续内容的首选库。它含有使数据清洗和分析工作变得更快更简单的数据结构和操作工具。
  • pandas 经常和其它工具一同使用,如数值计算工具 NumPy 和 SciPy ,分析库 statsmodels 和s cikit-learn,和数据可视化库 matplotlib。
  • pandas 是基于 NumPy 数组构建的,特别是基于数组的函数和不使用 for 循环的数据处理。
  • 虽然 pandas 采用了大量的 NumPy 编码风格,但二者最大的不同是 pandas 是专门为处理表格和混杂数据设计的。而 NumPy 更适合处理统一的数值数组数据。

在本书后续部分中,我将使用下面这样的 pandas 引入约定:

In [1]: import pandas as pd

# 因此,只要你在代码中看到pd.,就得想到这是 pandas 。
# 因为 Series 和 DataFrame 用的次数非常多,所以将其引入本地命名空间中会更方便:

In [2]: from pandas import Series, DataFrame

5.1 pandas 的数据结构介绍

  • 要使用 pandas ,你首先就得熟悉它的两个主要数据结构: Series 和 DataFrame 。
  • 虽然它们并不能解决所有问题,但它们为大多数应用提供了一种可靠的、易于使用的基础。

Series

Series 是一种类似于一维数组的对象,它由一组数据(各种 NumPy 数据类型)以及一组与之相关的数据标签(即索引)组成。仅由一组数据即可产生最简单的 Series :

In [5]: obj = pd.Series([4, 8, -2, 3])

In [6]: obj
Out[6]:
0    4
1    8
2   -2
3    3
dtype: int64

Series 的字符串表现形式为:索引在左边,值在右边。由于我们没有为数据指定索引,于是会自动创建一个 0 到 N-1( N 为数据的长度)的整数型索引。你可以通过 Series 的 values 和 index 属性获取其数组表示形式和索引对象:

In [7]: obj.values
Out[7]: array([ 4,  8, -2,  3], dtype=int64)

In [8]: obj.index  # like range(4)
Out[8]: RangeIndex(start=0, stop=4, step=1)

通常,我们希望所创建的 Series 带有一个可以对各个数据点进行标记的索引:

In [9]: obj2 = pd.Series([4, 7, -5, 3], index=['d', 'b', 'a', '
   ...: c'])

In [10]: obj2
Out[10]:
d    4
b    7
a   -5
c    3
dtype: int64

In [11]: obj2.index
Out[11]: Index(['d', 'b', 'a', 'c'], dtype='object')

与普通 NumPy 数组相比,你可以通过索引的方式选取 Series 中的单个或一组值:

In [12]: obj2['a']
Out[12]: -5

In [13]: obj2['b']
Out[13]: 7

In [14]: obj2[['c', 'd', 'a']]
Out[14]:
c    3
d    4
a   -5
dtype: int64

['c', 'd', 'a']是索引列表,即使它包含的是字符串而不是整数。

使用 NumPy 函数或类似 NumPy 的运算(如根据布尔型数组进行过滤、标量乘法、应用数学函数等)都会保留索引值的链接:

In [21]: obj2[obj2 > 0]
Out[21]:
d    4
b    7
c    3
dtype: int64

In [22]: obj2 * 2
Out[22]:
d     8
b    14
a   -10
c     6
dtype: int64

In [23]: import numpy as np

In [24]: np.exp(obj2)
Out[24]:
d      54.598150
b    1096.633158
a       0.006738
c      20.085537
dtype: float64

还可以将 Series 看成是一个定长的有序字典,因为它是索引值到数据值的一个映射。它可以用在许多原本需要字典参数的函数中:

In [25]: 'b' in obj2
Out[25]: True

In [26]: 'e' in obj2
Out[26]: False

# 如果数据被存放在一个 Python 字典中,也可以直接通过这个字典来创建 Series: 

In [27]: sdata = {'Ohio': 35000, 'Texas': 71000, 'Oregon': 1600
    ...: 0, 'Utah': 5000}

In [28]: obj3 = pd.Series(sdata)

In [29]: obj3
Out[29]:
Ohio      35000
Oregon    16000
Texas     71000
Utah       5000
dtype: int64

如果只传入一个字典,则结果 Series 中的索引就是原字典的键(有序排列)。你可以传入排好序的字典的键以改变顺序:

In [30]: states = ['California', 'Ohio', 'Oregon', 'Texas']

In [31]: obj4 = pd.Series(sdata, index=states)

In [32]: obj4
Out[32]:
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
dtype: float64

在这个例子中, sdata 中跟 states 索引相匹配的那 3 个值会被找出来并放到相应的位置上,但由于”California”所对应的 sdata 值找不到,所以其结果就为NaN(即“非数字”(not a number),在 pandas 中,它用于表示缺失或NA值)。因为‘Utah’不在 states 中,它被从结果中除去。

我将使用缺失(missing)或 NA 表示缺失数据。 pandas 的 isnull 和 notnull 函数可用于检测缺失数据:

In [33]: pd.isnull(obj4)
Out[33]:
California     True
Ohio          False
Oregon        False
Texas         False
dtype: bool

In [34]: pd.notnull(obj4)
Out[34]:
California    False
Ohio           True
Oregon         True
Texas          True
dtype: bool

Series 也有类似的实例方法:

In [35]: obj4.isnull()
Out[35]:
California     True
Ohio          False
Oregon        False
Texas         False
dtype: bool

In [36]: obj4.notnull()
Out[36]:
California    False
Ohio           True
Oregon         True
Texas          True
dtype: bool

我将在第7章详细讲解如何处理缺失数据。

对于许多应用而言, Series 最重要的一个功能是,它会根据运算的索引标签自动对齐数据:

In [37]: obj3
Out[37]:
Ohio      35000
Oregon    16000
Texas     71000
Utah       5000
dtype: int64

In [38]: obj4
Out[38]:
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
dtype: float64

In [39]: obj3 + obj4
Out[39]:
California         NaN
Ohio           70000.0
Oregon         32000.0
Texas         142000.0
Utah               NaN
dtype: float64

数据对齐功能将在后面详细讲解。如果你使用过数据库,你可以认为是类似join的操作。

Series 对象本身及其索引都有一个name属性,该属性跟 pandas 其他的关键功能关系非常密切:

In [43]: obj4.name = 'population'

In [44]: obj4.index.name= 'state'

In [45]: obj4
Out[45]:
state
California        NaN
Ohio          35000.0
Oregon        16000.0
Texas         71000.0
Name: population, dtype: float64

Series 的索引可以通过赋值的方式就地修改:

In [46]: obj
Out[46]:
0    4
1    8
2   -2
3    3
dtype: int64

In [47]: obj.index = ['Bob', 'Steve', 'Jeff', 'Ryan']
    ...:

In [48]: obj
Out[48]:
Bob      4
Steve    8
Jeff    -2
Ryan     3
dtype: int64

DataFrame

  • DataFrame 是一个表格型的数据结构,它含有一组有序的列,每列可以是不同的值类型(数值、字符串、布尔值等)。
  • DataFrame 既有行索引也有列索引,它可以被看做由 Series 组成的字典(共用同一个索引)。
  • DataFrame 中的数据是以一个或多个二维块存放的(而不是列表、字典或别的一维数据结构)。
  • 笔记:虽然 DataFrame 是以二维结构保存数据的,但你仍然可以轻松地将其表示为更高维度的数据(层次化索引的表格型结构,这是 pandas 中许多高级数据处理功能的关键要素,我们会在第8章讨论这个问题)。

建 DataFrame 的办法有很多,最常用的一种是直接传入一个由等长列表或 NumPy 数组组成的字典:

In [50]: data = {'state': ['Ohio', 'Ohio', 'Ohio', 'Nevada', 'N
    ...: evada', 'Nevada'],
    ...:         'year': [2000, 2001, 2002, 2001, 2002, 2003],
    ...:         'pop': [1.5, 1.7, 3.6, 2.4, 2.9, 3.2]}
    ...:

In [51]: frame = pd.DataFrame(data)

# 结果 DataFrame 会自动加上索引(跟 Series 一样),且全部列会被有序排列:

In [52]: frame
Out[52]:
   pop   state  year
0  1.5    Ohio  2000
1  1.7    Ohio  2001
2  3.6    Ohio  2002
3  2.4  Nevada  2001
4  2.9  Nevada  2002
5  3.2  Nevada  2003

如果你使用的是 Jupyter notebook, pandas DataFrame 对象会以对浏览器友好的 HTML 表格的方式呈现。

对于特别大的 DataFrame ,head方法会选取前五行:

In [53]: frame.head()
Out[53]:
   pop   state  year
0  1.5    Ohio  2000
1  1.7    Ohio  2001
2  3.6    Ohio  2002
3  2.4  Nevada  2001
4  2.9  Nevada  2002

如果指定了列序列,则 DataFrame 的列就会按照指定顺序进行排列:


In [54]: pd.DataFrame(data, columns=['year', 'state', 'pop'])
Out[54]:
   year   state  pop
0  2000    Ohio  1.5
1  2001    Ohio  1.7
2  2002    Ohio  3.6
3  2001  Nevada  2.4
4  2002  Nevada  2.9
5  2003  Nevada  3.2

# 如果传入的列在数据中找不到,就会在结果中产生缺失值:

In [55]: frame2 = pd.DataFrame(data, columns=['year','state','p
    ...: op','debt'], index=['one', 'two', 'three','four', 'fiv
    ...: e', 'six'])

In [56]: frame2
Out[56]:
       year   state  pop debt
one    2000    Ohio  1.5  NaN
two    2001    Ohio  1.7  NaN
three  2002    Ohio  3.6  NaN
four   2001  Nevada  2.4  NaN
five   2002  Nevada  2.9  NaN
six    2003  Nevada  3.2  NaN

In [57]: frame2.columns
Out[57]: Index(['year', 'state', 'pop', 'debt'], dtype='object')

通过类似字典标记的方式或属性的方式,可以将 DataFrame 的列获取为一个 Series :

In [58]: frame2['state']
Out[58]:
one        Ohio
two        Ohio
three      Ohio
four     Nevada
five     Nevada
six      Nevada
Name: state, dtype: object

In [59]: frame2
Out[59]:
       year   state  pop debt
one    2000    Ohio  1.5  NaN
two    2001    Ohio  1.7  NaN
three  2002    Ohio  3.6  NaN
four   2001  Nevada  2.4  NaN
five   2002  Nevada  2.9  NaN
six    2003  Nevada  3.2  NaN

In [60]: frame.year
Out[60]:
0    2000
1    2001
2    2002
3    2001
4    2002
5    2003
Name: year, dtype: int64

In [61]: frame2.year
Out[61]:
one      2000
two      2001
three    2002
four     2001
five     2002
six      2003
Name: year, dtype: int64

  • 笔记:IPython 提供了类似属性的访问(即frame2.year)和 tab 补全。
  • frame2[column]适用于任何列的名,但是frame2.column只有在列名是一个合理的 Python 变量名时才适用。

注意,返回的 Series 拥有原 DataFrame 相同的索引,且其 name 属性也已经被相应地设置好了。

行也可以通过位置或名称的方式进行获取,比如用 loc 属性(稍后将对此进行详细讲解):

In [62]: frame2.loc['four']
Out[62]:
year       2001
state    Nevada
pop         2.4
debt        NaN
Name: four, dtype: object

列可以通过赋值的方式进行修改。例如,我们可以给那个空的”debt”列赋上一个标量值或一组值:

In [63]: frame2['debt'] = 16.5

In [64]: frame2
Out[64]:
       year   state  pop  debt
one    2000    Ohio  1.5  16.5
two    2001    Ohio  1.7  16.5
three  2002    Ohio  3.6  16.5
four   2001  Nevada  2.4  16.5
five   2002  Nevada  2.9  16.5
six    2003  Nevada  3.2  16.5

In [65]: frame2['debt'] = np.arange(6.)

In [66]: frame2
Out[66]:
       year   state  pop  debt
one    2000    Ohio  1.5   0.0
two    2001    Ohio  1.7   1.0
three  2002    Ohio  3.6   2.0
four   2001  Nevada  2.4   3.0
five   2002  Nevada  2.9   4.0
six    2003  Nevada  3.2   5.0

将列表或数组赋值给某个列时,其长度必须跟 DataFrame 的长度相匹配。如果赋值的是一个 Series ,就会精确匹配 DataFrame 的索引,所有的空位都将被填上缺失值:

In [67]: val = pd.Series([-1.2, -1.5, -1.7], index=['two', 'fou
    ...: r', 'five'])

In [68]: frame2['debt'] = val

In [69]: frame2
Out[69]:
       year   state  pop  debt
one    2000    Ohio  1.5   NaN
two    2001    Ohio  1.7  -1.2
three  2002    Ohio  3.6   NaN
four   2001  Nevada  2.4  -1.5
five   2002  Nevada  2.9  -1.7
six    2003  Nevada  3.2   NaN

为不存在的列赋值会创建出一个新列。关键字 del 用于删除列。

作为 del 的例子,我先添加一个新的布尔值的列,state 是否为’Ohio’:

In [70]: frame2['eastern'] = frame2.state == 'Ohio'

In [71]: frame2
Out[71]:
       year   state  pop  debt  eastern
one    2000    Ohio  1.5   NaN     True
two    2001    Ohio  1.7  -1.2     True
three  2002    Ohio  3.6   NaN     True
four   2001  Nevada  2.4  -1.5    False
five   2002  Nevada  2.9  -1.7    False
six    2003  Nevada  3.2   NaN    False

注意:不能用 frame2.eastern 创建新的列。

del 方法可以用来删除这列:

In [75]: frame2['eastern'] = frame2.state == 'Ohio'

In [76]: del frame2['eastern']

In [77]: frame2.columns
Out[77]: Index(['year', 'state', 'pop', 'debt'], dtype='object')

  • 注意:通过索引方式返回的列只是相应数据的视图而已,并不是副本。因此,对返回的 Series 所做的任何就地修改全都会反映到源 DataFrame 上。通过 Series 的copy方法即可指定复制列。

另一种常见的数据形式是嵌套字典:

In [78]: pop = {'Nevada': {2001: 2.4, 2002: 2.9},
    ...: ....:        'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}
    ...: }
    ...:

# 如果嵌套字典传给 DataFrame , pandas 就会被解释为:外层字典的键作为列,内层键则作为行索引:

In [79]: frame3 = pd.DataFrame(pop)

In [80]: frame3
Out[80]:
      Nevada  Ohio
2000     NaN   1.5
2001     2.4   1.7
2002     2.9   3.6

# 你也可以使用类似NumPy数组的方法,对DataFrame进行转置(交换行和列):

In [81]: frame3.T
Out[81]:
        2000  2001  2002
Nevada   NaN   2.4   2.9
Ohio     1.5   1.7   3.6

# 内层字典的键会被合并、排序以形成最终的索引。如果明确指定了索引,则不会这样:

In [82]: pd.DataFrame(pop, index=[2001, 2002, 2003])
Out[82]:
      Nevada  Ohio
2001     2.4   1.7
2002     2.9   3.6
2003     NaN   NaN

由 Series 组成的字典差不多也是一样的用法:


In [83]: pdata = {'Ohio': frame3['Ohio'][:-1],'Nevada':frame3['
    ...: Nevada'][:2]}

In [84]: pd.DataFrame(pdata)
Out[84]:
      Nevada  Ohio
2000     NaN   1.5
2001     2.4   1.7

In [85]: frame3
Out[85]:
      Nevada  Ohio
2000     NaN   1.5
2001     2.4   1.7
2002     2.9   3.6

  • frame3['Ohio'][:-1] 从索引 0 开始到 最后一个,不包含最后一个
  • frame3['Nevada'][:2] 从索引 0 开始到索引 2,不包含 2

表 5-1 列出了 DataFrame 构造函数所能接受的各种数据。

Python for Data Analysis v2 | Notes_ Chapter_5 pandas 入门

如果设置了 DataFrame 的 index 和 columns 的 name 属性,则这些信息也会被显示出来:

In [86]: frame3.index.name = 'year';frame3.columns.name = 'stat
    ...: e'

In [87]: frame3
Out[87]:
state  Nevada  Ohio
year
2000      NaN   1.5
2001      2.4   1.7
2002      2.9   3.6

# 跟 Series 一样,values 属性也会以二维 ndarray 的形式返回 DataFrame 中的数据:

In [88]: frame3.values
Out[88]:
array([[nan, 1.5],
       [2.4, 1.7],
       [2.9, 3.6]])

如果 DataFrame 各列的数据类型不同,则值数组的 dtype 就会选用能兼容所有列的数据类型:

In [89]: frame2.values
Out[89]:
array([[2000, 'Ohio', 1.5, nan],
       [2001, 'Ohio', 1.7, -1.2],
       [2002, 'Ohio', 3.6, nan],
       [2001, 'Nevada', 2.4, -1.5],
       [2002, 'Nevada', 2.9, -1.7],
       [2003, 'Nevada', 3.2, nan]], dtype=object)

索引对象

pandas 的索引对象负责管理轴标签和其他元数据(比如轴名称等)。构建 Series 或 DataFrame 时,所用到的任何数组或其他序列的标签都会被转换成一个 Index:

In [90]: obj = pd.Series(range(3), index=['a', 'b', 'c'])\
    ...:

In [91]: index = obj.index

In [92]: obj
Out[92]:
a    0
b    1
c    2
dtype: int32

In [93]: index
Out[93]: Index(['a', 'b', 'c'], dtype='object')

In [94]: index[1:]
Out[94]: Index(['b', 'c'], dtype='object')

# Index对象是不可变的,因此用户不能对其进行修改

In [95]: index[1] = 'd'
----------------------------------------------------------------

TypeError                      Traceback (most recent call last)

<ipython-input-95-a452e55ce13b> in <module>()
----> 1 index[1] = 'd'

d:\program files\python36\lib\site-packages\pandas\core\indexes\
base.py in __setitem__(self, key, value)
   1668
   1669     def __setitem__(self, key, value):
-> 1670         raise TypeError("Index does not support mutable
operations")
   1671
   1672     def __getitem__(self, key):

TypeError: Index does not support mutable operations

In [96]:

不可变可以使 Index 对象在多个数据结构之间安全共享:

In [99]: labels = pd.Index(np.arange(3))

In [100]: labels
Out[100]: Int64Index([0, 1, 2], dtype='int64')

In [101]: obj2 = pd.Series([1.5, -2.5, 0], index=labels)

In [102]: obj2
Out[102]:
0    1.5
1   -2.5
2    0.0
dtype: float64

In [103]: obj2.index is labels
Out[103]: True
  • 注意:虽然用户不需要经常使用Index的功能,但是因为一些操作会生成包含被索引化的数据,理解它们的工作原理是很重要的。

除了类似于数组,Index 的功能也类似一个固定大小的集合:

In [104]: frame3
Out[104]:
state  Nevada  Ohio
year
2000      NaN   1.5
2001      2.4   1.7
2002      2.9   3.6

In [105]: frame3.columns
Out[105]: Index(['Nevada', 'Ohio'], dtype='object', name='state'
)

In [106]: 'Ohio' in frame3.columns
Out[106]: True

In [107]: 2003 in frame3.index
Out[107]: False

与 Python 的集合不同, pandas 的Index可以包含重复的标签:

In [113]: dup_labels = pd.Index(['foo', 'foo', 'bar', 'bar'])

In [114]: dup_labels
Out[114]: Index(['foo', 'foo', 'bar', 'bar'], dtype='object')

选择重复的标签,会显示所有的结果。

每个索引都有一些方法和属性,它们可用于设置逻辑并回答有关该索引所包含的数据的常见问题。表5-2列出了这些函数。

Python for Data Analysis v2 | Notes_ Chapter_5 pandas 入门

5.2 基本功能

本节中,我将介绍操作 Series 和 DataFrame 中的数据的基本手段。后续章节将更加深入地挖掘 pandas 在数据分析和处理方面的功能。本书不是 pandas 库的详尽文档,主要关注的是最重要的功能,那些不大常用的内容(也就是那些更深奥的内容)就交给你自己去摸索吧。

重新索引

pandas 对象的一个重要方法是 reindex,其作用是创建一个新对象,它的数据符合新的索引。看下面的例子:

In [124]: obj = pd.Series([4.5, 7.2, -5.3, 3.6], index=['d', 'b
     ...: ', 'a', 'c'])

In [125]: obj
Out[125]:
d    4.5
b    7.2
a   -5.3
c    3.6
dtype: float64

# 用该 Series 的 reindex将会根据新索引进行重排。
# 如果某个索引值当前不存在,就引入缺失值:
In [126]: obj2 = obj.reindex(['a', 'b', 'c', 'd', 'e'])

In [127]: obj2
Out[127]:
a   -5.3
b    7.2
c    3.6
d    4.5
e    NaN
dtype: float64

对于时间序列这样的有序数据,重新索引时可能需要做一些插值处理。method 选项即可达到此目的,例如,使用 ffill 可以实现前向值填充:

In [128]: obj3 = pd.Series(['blue', 'purple', 'yellow'], index=
     ...: [0,2,4])

In [129]: obj3
Out[129]:
0      blue
2    purple
4    yellow
dtype: object

In [130]: obj3.reindex(range(6), method='ffill')
Out[130]:
0      blue
1      blue
2    purple
3    purple
4    yellow
5    yellow
dtype: object

借助 DataFrame ,reindex 可以修改(行)索引和列。只传递一个序列时,会重新索引结果的行:

In [131]: frame = pd.DataFrame(np.arange(9).reshape((3,3)),inde
     ...: x=['a','c','d'],columns=['Ohio','Texas','California']
     ...: )

In [132]: frame
Out[132]:
   Ohio  Texas  California
a     0      1           2
c     3      4           5
d     6      7           8

In [133]: frame2 = frame.reindex(['a','b','c','d'])

In [134]: frame2
Out[134]:
   Ohio  Texas  California
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

列可以用 columns 关键字重新索引:

In [135]: states = ['Texas', 'Utah', 'California']

In [136]: frame.reindex(columns=states)
Out[136]:
   Texas  Utah  California
a      1   NaN           2
c      4   NaN           5
d      7   NaN           8

表5-3列出了 reindex 函数的各参数及说明。

Python for Data Analysis v2 | Notes_ Chapter_5 pandas 入门

丢弃指定轴上的项

丢弃某条轴上的一个或多个项很简单,只要有一个索引数组或列表即可。由于需要执行一些数据整理和集合逻辑,所以 drop 方法返回的是一个在指定轴上删除了指定值的新对象:

In [137]: obj = pd.Series(np.arange(5.), index=['a', 'b', 'c',
     ...: 'd', 'e'])

In [138]: obj
Out[138]:
a    0.0
b    1.0
c    2.0
d    3.0
e    4.0
dtype: float64

In [139]: new_obj = obj.drop('c')

In [140]: new_obj
Out[140]:
a    0.0
b    1.0
d    3.0
e    4.0
dtype: float64

In [141]: obj.drop(['d', 'c'])
Out[141]:
a    0.0
b    1.0
e    4.0
dtype: float64

对于 DataFrame ,可以删除任意轴上的索引值。为了演示,先新建一个 DataFrame 例子:


In [142]: data = pd.DataFrame(np.arange(16).reshape((4,4)), ind
     ...: ex=['Ohio', 'Colorado', 'Utah', 'New York'],columns=[
     ...: 'one', 'two', 'three', 'four'])

In [143]: data
Out[143]:
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

用标签序列调用 drop 会从行标签(axis 0)删除值:

In [144]: data.drop(['Colorado', 'Ohio'])
Out[144]:
          one  two  three  four
Utah        8    9     10    11
New York   12   13     14    15

通过传递 axis=1axis='columns' 可以删除列的值:

In [145]: data.drop('two', axis=1)
Out[145]:
          one  three  four
Ohio        0      2     3
Colorado    4      6     7
Utah        8     10    11
New York   12     14    15

In [146]: data.drop(['two', 'four'], axis='columns')
Out[146]:
          one  three
Ohio        0      2
Colorado    4      6
Utah        8     10
New York   12     14

许多函数,如 drop,会修改 Series 或 DataFrame 的大小或形状,可以就地修改对象,不会返回新的对象:

In [147]: obj.drop('c', inplace=True)

In [148]: obj
Out[148]:
a    0.0
b    1.0
d    3.0
e    4.0
dtype: float64
  • 小心使用 inplace,它会销毁所有被删除的数据。

索引、选取和过滤

Series 索引(obj[…])的工作方式类似于 NumPy 数组的索引,只不过 Series 的索引值不只是整数。下面是几个例子:

In [149]: obj = pd.Series(np.arange(4.), index=['a', 'b', 'c',
     ...: 'd'])
     ...:

In [150]: obj
Out[150]:
a    0.0
b    1.0
c    2.0
d    3.0
dtype: float64

In [151]: obj['b']
Out[151]: 1.0

In [152]: obj[1]
Out[152]: 1.0

In [153]: obj[2:4]
Out[153]:
c    2.0
d    3.0
dtype: float64

In [154]: obj[['b', 'a', 'd']]
Out[154]:
b    1.0
a    0.0
d    3.0
dtype: float64

In [155]: obj[[1,3]]
Out[155]:
b    1.0
d    3.0
dtype: float64

In [156]: obj[obj<2]
Out[156]:
a    0.0
b    1.0
dtype: float64

利用标签的切片运算与普通的 Python 切片运算不同,其末端是包含的:

In [157]: obj['b':'c']
Out[157]:
b    1.0
c    2.0
dtype: float64

# 用切片可以对 Series 的相应部分进行设置:

In [158]: obj['b':'c'] = 5

In [159]: obj
Out[159]:
a    0.0
b    5.0
c    5.0
d    3.0
dtype: float64

用一个值或序列对 DataFrame 进行索引其实就是获取一个或多个列:

In [160]: data = pd.DataFrame(np.arange(16).reshape((4, 4)),
     ...:    .....:                     index=['Ohio', 'Colorad
     ...: o', 'Utah', 'New York'],
     ...:    .....:                     columns=['one', 'two',
     ...: 'three', 'four'])
     ...:

In [161]:

In [161]: data
Out[161]:
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

In [162]: data['two']
Out[162]:
Ohio         1
Colorado     5
Utah         9
New York    13
Name: two, dtype: int32

In [163]: data[['two','four']]
Out[163]:
          two  four
Ohio        1     3
Colorado    5     7
Utah        9    11
New York   13    15

这种索引方式有几个特殊的情况。首先通过切片或布尔型数组选取数据:

In [2]: import pandas as pd

In [3]: import numpy as np

In [4]: data = pd.DataFrame(np.arange(16).reshape((4, 4)),
   ...:    .....:                     index=['Ohio', 'Colorado'
   ...: , 'Utah', 'New York'],
   ...:    .....:                     columns=['one', 'two', 't
   ...: hree', 'four'])
   ...:

In [5]: data[:2]
Out[5]:
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7

In [6]: data[:2:3]
Out[6]:
      one  two  three  four
Ohio    0    1      2     3

In [7]: data[data['three'] > 5]
Out[7]:
          one  two  three  four
Colorado    4    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

选取行的语法 data[:2]十分方便。向[ ]传递单一的元素或列表,就可选择列。

另一种用法是通过布尔型 DataFrame (比如下面这个由标量比较运算得出的)进行索引:

In [9]: data < 5
Out[9]:
            one    two  three   four
Ohio       True   True   True   True
Colorado   True  False  False  False
Utah      False  False  False  False
New York  False  False  False  False

In [10]: data[data < 5] = 0

In [11]: data
Out[11]:
          one  two  three  four
Ohio        0    0      0     0
Colorado    0    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

这使得 DataFrame 的语法与 NumPy 二维数组的语法很像。

用 loc 和 iloc 进行选取

对于 DataFrame 的行的标签索引,我引入了特殊的标签运算符 loc 和 iloc。它们可以让你用类似 NumPy 的标记,使用轴标签(loc)或整数索引(iloc),从 DataFrame 选择行和列的子集。

作为一个初步示例,让我们通过标签选择一行和多列:

In [7]: data.loc['Colorado', ['two', 'three']]
Out[7]:
two      5
three    6
Name: Colo rado, dtype: int32

然后用 iloc和整数进行选取:

In [8]: data
Out[8]:
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11
New York   12   13     14    15

In [9]: data.iloc[2, [3, 0, 1]]
Out[9]:
four    11
one      8
two      9
Name: Utah, dtype: int32

In [10]: data.iloc[2]
Out[10]:
one       8
two       9
three    10
four     11
Name: Utah, dtype: int32

In [11]: data.iloc[[1,2], [3, 0, 1]]
Out[11]:
          four  one  two
Colorado     7    4    5
Utah        11    8    9

这两个索引函数也适用于一个标签或多个标签的切片:

In [12]: data.loc[:'Utah', 'two']
Out[12]:
Ohio        1
Colorado    5
Utah        9
Name: two, dtype: int32

In [13]: data.iloc[:, :3][data.three > 5]
Out[13]:
          one  two  three
Colorado    4    5      6
Utah        8    9     10
New York   12   13     14

所以,在 pandas 中,有多个方法可以选取和重新组合数据。对于 DataFrame ,表5-4 进行了总结。后面会看到,还有更多的方法进行层级化索引。

  • 笔记:在一开始设计 pandas 时,我觉得用frame[:, col]选取列过于繁琐(也容易出错),因为列的选择是非常常见的操作。我做了些取舍,将花式索引的功能(标签和整数)放到了 ix 运算符中。在实践中,这会导致许多边缘情况,数据的轴标签是整数,所以 pandas 团队决定创造lociloc运算符分别处理严格基于标签和整数的索引。
    ix 运算符仍然可用,但并不推荐。

Python for Data Analysis v2 | Notes_ Chapter_5 pandas 入门

整数索引

处理整数索引的 pandas 对象常常难住新手,因为它与 Python 内置的列表和元组的索引语法不同。例如,你可能不认为下面的代码会出错:

In [14]: ser = pd.Series(np.arange(3.))

In [15]: ser
Out[15]:
0    0.0
1    1.0
2    2.0
dtype: float64

In [16]: ser[-1]
----------------------------------------------------------------

KeyError                       Traceback (most recent call last)

<ipython-input-16-44969a759c20> in <module>()
----> 1 ser[-1]

d:\program files\python36\lib\site-packages\pandas\core\series.p
y in __getitem__(self, key)
    599         key = com._apply_if_callable(key, self)
    600         try:
--> 601             result = self.index.get_value(self, key)
    602
    603             if not is_scalar(result):

d:\program files\python36\lib\site-packages\pandas\core\indexes\
base.py in get_value(self, series, key)
   2475         try:
   2476             return self._engine.get_value(s, k,
-> 2477                                           tz=getattr(ser
ies.dtype, 'tz', None))
   2478         except KeyError as e1:
   2479             if len(self) > 0 and self.inferred_type in [
'integer', 'boolean']:

pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_val
ue (pandas\_libs\index.c:4404)()

pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_val
ue (pandas\_libs\index.c:4087)()

pandas\_libs\index.pyx in pandas._libs.index.IndexEngine.get_loc
 (pandas\_libs\index.c:5126)()

pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtabl
e.Int64HashTable.get_item (pandas\_libs\hashtable.c:14031)()

pandas\_libs\hashtable_class_helper.pxi in pandas._libs.hashtabl
e.Int64HashTable.get_item (pandas\_libs\hashtable.c:13975)()

KeyError: -1

这里, pandas 可以勉强进行整数索引,但是会导致小bug。我们有包含0,1,2的索引,但是引入用户想要的东西(基于标签或位置的索引)很难:

In [17]: ser
Out[17]:
0    0.0
1    1.0
2    2.0
dtype: float64

另外,对于非整数索引,不会产生歧义:

In [18]: ser2 = pd.Series(np.arange(3.), index=['a','b','c'])

In [19]: ser2[-1]
Out[19]: 2.0

为了进行统一,如果轴索引含有整数,数据选取总会使用标签。为了更准确,请使用 loc(标签)或 iloc(整数):

In [20]: ser[:1]
Out[20]:
0    0.0
dtype: float64

In [21]: ser
Out[21]:
0    0.0
1    1.0
2    2.0
dtype: float64

In [22]: ser.loc[:1]
Out[22]:
0    0.0
1    1.0
dtype: float64

In [23]: ser.iloc[:1]
Out[23]:
0    0.0
dtype: float64

算术运算和数据对齐

pandas 最重要的一个功能是,它可以对不同索引的对象进行算术运算。在将对象相加时,如果存在不同的索引对,则结果的索引就是该索引对的并集。对于有数据库经验的用户,这就像在索引标签上进行自动外连接。看一个简单的例子:

In [24]: s1 = pd.Series([7.3, -2.5, 3.4, 1.5],index=['a', 'c',
    ...: 'd', 'e'])

In [25]: s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1],
    ...:    .....:                index=['a', 'c', 'e', 'f', 'g
    ...: '])
    ...:

In [26]: s1
Out[26]:
a    7.3
c   -2.5
d    3.4
e    1.5
dtype: float64

In [27]: s2
Out[27]:
a   -2.1
c    3.6
e   -1.5
f    4.0
g    3.1
dtype: float64

将它们相加就会产生:

In [28]: s1 + s2
Out[28]:
a    5.2
c    1.1
d    NaN
e    0.0
f    NaN
g    NaN
dtype: float64

自动的数据对齐操作在不重叠的索引处引入了NA值。缺失值会在算术运算过程中传播。

对于 DataFrame ,对齐操作会同时发生在行和列上:

In [29]: df1 = pd.DataFrame(np.arange(9).reshape((3,3)),columns
    ...: =list('bcd'), index=['Ohio', 'Texas', 'Colorado'])

In [30]: df2 = pd.DataFrame(np.arange(12.).reshape((4, 3)),colu
    ...: mns=list('bde'),index=['Utah', 'Ohio', 'Texas', 'Orego
    ...: n'])

In [31]: df1
Out[31]:
          b  c  d
Ohio      0  1  2
Texas     3  4  5
Colorado  6  7  8

In [32]: df2
Out[32]:
          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0

把它们相加后将会返回一个新的 DataFrame ,其索引和列为原来那两个 DataFrame 的并集:

In [33]: df1 + df2
Out[33]:
            b   c     d   e
Colorado  NaN NaN   NaN NaN
Ohio      3.0 NaN   6.0 NaN
Oregon    NaN NaN   NaN NaN
Texas     9.0 NaN  12.0 NaN
Utah      NaN NaN   NaN NaN

因为’c’和’e’列均不在两个 DataFrame 对象中,在结果中以缺省值呈现。行也是同样。

如果 DataFrame 对象相加,没有共用的列或行标签,结果都会是空:

In [38]: df1 = pd.DataFrame({'A':[1,2]})

In [39]: df2 = pd.DataFrame({'B':[3,4], 'c':[5,6]})

In [40]: df1 + df2
Out[40]:
    A   B   c
0 NaN NaN NaN
1 NaN NaN NaN

在算术方法中填充值

在对不同索引的对象进行算术运算时,你可能希望当一个对象中某个轴标签在另一个对象中找不到时填充一个特殊值(比如 0):

In [41]: df1 = pd.DataFrame(np.arange(12.).reshape((3,4)),colum
    ...: ns=list('abcd'))

In [42]: df2 = pd.DataFrame(np.arange(20.).reshape((4,5)),colum
    ...: ns=list('abcde'))

In [43]: df2.loc[1, 'b'] = np.nan

In [44]: df2
Out[44]:
      a     b     c     d     e
0   0.0   1.0   2.0   3.0   4.0
1   5.0   NaN   7.0   8.0   9.0
2  10.0  11.0  12.0  13.0  14.0
3  15.0  16.0  17.0  18.0  19.0

In [45]: df1
Out[45]:
     a    b     c     d
0  0.0  1.0   2.0   3.0
1  4.0  5.0   6.0   7.0

将它们相加时,没有重叠的位置就会产生NA值:

In [52]: df1 + df2
Out[52]:
      a     b     c     d   e
0   0.0   2.0   4.0   6.0 NaN
1   9.0   NaN  13.0  15.0 NaN
2  18.0  20.0  22.0  24.0 NaN
3   NaN   NaN   NaN   NaN NaN


# 使用df1的add方法,传入df2以及一个fill_value参数:
In [53]: df1.add(df2, fill_value=0)
Out[53]:
      a     b     c     d     e
0   0.0   2.0   4.0   6.0   4.0
1   9.0   5.0  13.0  15.0   9.0
2  18.0  20.0  22.0  24.0  14.0
3  15.0  16.0  17.0  18.0  19.0

表5-5列出了 Series 和 DataFrame 的算术方法。它们每个都有一个副本,以字母 r 开头,它会翻转参数。因此这两个语句是等价的:

In [54]: 1 / df1
Out[54]:
          a         b         c         d
0       inf  1.000000  0.500000  0.333333
1  0.250000  0.200000  0.166667  0.142857
2  0.125000  0.111111  0.100000  0.090909

In [55]: df1.rdiv(1)
Out[55]:
          a         b         c         d
0       inf  1.000000  0.500000  0.333333
1  0.250000  0.200000  0.166667  0.142857
2  0.125000  0.111111  0.100000  0.090909

Python for Data Analysis v2 | Notes_ Chapter_5 pandas 入门

与此类似,在对 Series 或 DataFrame 重新索引时,也可以指定一个填充值:

In [57]: df1.reindex(columns=df2.columns, fill_value=0)
Out[57]:
     a    b     c     d  e
0  0.0  1.0   2.0   3.0  0
1  4.0  5.0   6.0   7.0  0
2  8.0  9.0  10.0  11.0  0

DataFrame 和 Series 之间的运算

跟不同维度的 NumPy 数组一样, DataFrame 和 Series 之间算术运算也是有明确规定的。先来看一个具有启发性的例子,计算一个二维数组与其某行之间的差:

In [58]: arr = np.arange(12.).reshape(3,4)

In [59]: arr
Out[59]:
array([[ 0.,  1.,  2.,  3.],
       [ 4.,  5.,  6.,  7.],
       [ 8.,  9., 10., 11.]])

In [60]: arr[0]
Out[60]: array([0., 1., 2., 3.])

In [61]: arr - arr[0]
Out[61]:
array([[0., 0., 0., 0.],
       [4., 4., 4., 4.],
       [8., 8., 8., 8.]])

当我们从arr减去arr[0],每一行都会执行这个操作。这就叫做广播(broadcasting),附录A 将对此进行详细讲解。 DataFrame 和 Series 之间的运算差不多也是如此:

In [66]: frame = pd.DataFrame(np.arange(12.).reshape((4, 3)),
    ...:    .....:                      columns=list('bde'),
    ...:    .....:                      index=['Utah', 'Ohio',
    ...: 'Texas', 'Oregon'])
    ...:

In [67]: series = frame.iloc[0]

In [68]: series
Out[68]:
b    0.0
d    1.0
e    2.0
Name: Utah, dtype: float64

默认情况下, DataFrame 和 Series 之间的算术运算会将 Series 的索引匹配到 DataFrame 的列,然后沿着行一直向下广播:

In [69]: frame - series
Out[69]:
          b    d    e
Utah    0.0  0.0  0.0
Ohio    3.0  3.0  3.0
Texas   6.0  6.0  6.0
Oregon  9.0  9.0  9.0

如果某个索引值在 DataFrame 的列或 Series 的索引中找不到,则参与运算的两个对象就会被重新索引以形成并集:

In [70]: series2 = pd.Series(range(3), index=['b', 'e', 'f'])

In [71]: frame + series2
Out[71]:
          b   d     e   f
Utah    0.0 NaN   3.0 NaN
Ohio    3.0 NaN   6.0 NaN
Texas   6.0 NaN   9.0 NaN
Oregon  9.0 NaN   12.0 NaN

如果你希望匹配行且在列上广播,则必须使用算术运算方法。例如:

In [72]: series3 = frame['d']

In [73]: frame
Out[73]:
          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0

In [74]: series3
Out[74]:
Utah       1.0
Ohio       4.0
Texas      7.0
Oregon    10.0
Name: d, dtype: float64

In [75]: frame.sub(series3, axis='index')
Out[75]:
          b    d    e
Utah   -1.0  0.0  1.0
Ohio   -1.0  0.0  1.0
Texas  -1.0  0.0  1.0
Oregon -1.0  0.0  1.0

传入的轴号就是希望匹配的轴。在本例中,我们的目的是匹配 DataFrame 的行索引(axis='index' or axis=0)并进行广播。

函数应用和映射

NumPy 的 ufuncs(元素级数组方法)也可用于操作 pandas 对象:

In [76]: frame = pd.DataFrame(np.random.randn(4, 3), columns=li
    ...: st('bde'),
    ...:    .....:                      index=['Utah', 'Ohio',
    ...: 'Texas', 'Oregon'])
    ...:

In [77]: frame
Out[77]:
               b         d         e
Utah   -0.369429 -0.246136  0.010237
Ohio    1.230315  0.039495  0.676055
Texas   1.423312  0.129776  0.902086
Oregon  1.131370  1.253313 -0.237300

In [78]: np.abs(frame)
Out[78]:
               b         d         e
Utah    0.369429  0.246136  0.010237
Ohio    1.230315  0.039495  0.676055
Texas   1.423312  0.129776  0.902086
Oregon  1.131370  1.253313  0.237300

另一个常见的操作是,将函数应用到由各列或行所形成的一维数组上。 DataFrame 的 apply 方法即可实现此功能:

In [79]: f = lambda x: x.max() - x.min()

In [80]: frame.apply(f)
Out[80]:
b    1.792741
d    1.499449
e    1.139387
dtype: float64

这里的函数f,计算了一个 Series 的最大值和最小值的差,在 frame 的每列都执行了一次。结果是一个 Series ,使用 frame 的列作为索引。

如果传递axis='columns'到 apply,这个函数会在每行执行:

In [81]: frame.apply(f, axis='columns')
Out[81]:
Utah      0.379666
Ohio      1.190820
Texas     1.293536
Oregon    1.490613
dtype: float64

许多最为常见的数组统计功能都被实现成 DataFrame 的方法(如summean),因此无需使用apply方法。

传递到apply的函数不是必须返回一个标量,还可以返回由多个值组成的 Series :

In [82]: def f(x):
    ...:     return pd.Series([x.min(), x.max()], index=['min', 'max'])
    ...:

In [83]: frame.apply(f)
Out[83]:
            b         d         e
min -0.369429 -0.246136 -0.237300
max  1.423312  1.253313  0.902086


In [84]: frame
Out[84]:
               b         d         e
Utah   -0.369429 -0.246136  0.010237
Ohio    1.230315  0.039495  0.676055
Texas   1.423312  0.129776  0.902086
Oregon  1.131370  1.253313 -0.237300

元素级的 Python 函数也是可以用的。假如你想得到frame中各个浮点值的格式化字符串,使用applymap即可:

In [85]: format = lambda x: '%.2f' % x

In [86]: frame.applymap(format)
Out[86]:
            b      d      e
Utah    -0.37  -0.25   0.01
Ohio     1.23   0.04   0.68
Texas    1.42   0.13   0.90
Oregon   1.13   1.25  -0.24

之所以叫做applymap,是因为 Series 有一个用于应用元素级函数的map方法

In [87]: frame['e'].map(format)
Out[87]:
Utah       0.01
Ohio       0.68
Texas      0.90
Oregon    -0.24
Name: e, dtype: object

In [88]: frame
Out[88]:
      b         d         e
Utah   -0.369429 -0.246136  0.010237
Ohio    1.230315  0.039495  0.676055
Texas   1.423312  0.129776  0.902086
Oregon  1.131370  1.253313 -0.237300

排序和排名

根据条件对数据集排序(sorting)也是一种重要的内置运算。要对行或列索引进行排序(按字典顺序),可使用sort_index方法,它将返回一个已排序的新对象:

In [89]: obj = pd.Series(range(4), index=['d', 'a', 'b', 'c'])

In [90]: obj.sort_index()
Out[90]:
a    1
b    2
c    3
d    0
dtype: int32

In [91]: obj
Out[91]:
d    0
a    1
b    2
c    3
dtype: int32

对于 DataFrame ,则可以根据任意一个轴上的索引进行排序:

In [93]: frame = pd.DataFrame(np.arange(8).reshape((2,4)), index=['three
    ...: ', 'one'], columns=['d', 'a', 'b', 'c'])

In [94]: frame.sort_index()
Out[94]:
       d  a  b  c
one    4  5  6  7
three  0  1  2  3

In [95]: frame.sort_index(axis=1)
Out[95]:
       a  b  c  d
three  1  2  3  0
one    5  6  7  4

数据默认是按升序排序的,但也可以降序排序:

In [96]: frame.sort_index(axis=1, ascending=False)
Out[96]:
       d  c  b  a
three  0  3  2  1
one    4  7  6  5

若要按值对 Series 进行排序,可使用其sort_values方法:

In [97]: obj = pd.Series([4, 7, -3, 2])

In [98]: obj.sort_values()
Out[98]:
2   -3
3    2
0    4
1    7
dtype: int64

在排序时,任何缺失值默认都会被放到 Series 的末尾:

In [99]: obj = pd.Series([4, np.nan, 7, np.nan, -3, 2])

In [100]: obj
Out[100]:
0    4.0
1    NaN
2    7.0
3    NaN
4   -3.0
5    2.0
dtype: float64

In [101]: obj.sort_index()
Out[101]:
0    4.0
1    NaN
2    7.0
3    NaN
4   -3.0
5    2.0
dtype: float64

In [102]: obj.sort_values()
Out[102]:
4   -3.0
5    2.0
0    4.0
2    7.0
1    NaN
3    NaN
dtype: float64

In [103]: obj.sort_values(ascending=False)
Out[103]:
2    7.0
0    4.0
5    2.0
4   -3.0
1    NaN
3    NaN
dtype: float64

当排序一个 DataFrame 时,你可能希望根据一个或多个列中的值进行排序。将一个或多个列的名字传递给sort_valuesby选项即可达到该目的:

In [104]: frame = pd.DataFrame({'b':[4,7,-3,2], 'a':[0, 1, 0, 1]})

In [105]: frame
Out[105]:
   a  b
0  0  4
1  1  7
2  0 -3
3  1  2

In [106]: frame.sort_values(by='b')
Out[106]:
   a  b
2  0 -3
3  1  2
0  0  4
1  1  7

要根据多个列进行排序,传入名称的列表即可:

In [107]: frame.sort_values(by=['a','b'])
Out[107]:
   a  b
2  0 -3
0  0  4
3  1  2
1  1  7

排名会从 1 开始一直到数组中有效数据的数量。接下来介绍 Series 和 DataFrame 的rank方法。默认情况下,rank 是通过“为各组分配一个平均排名”的方式破坏平级关系的:

In [108]: obj = pd.Series([7, -5, 7, 4, 2, 0, 4])

In [109]: obj.rank()
Out[109]:
0    6.5
1    1.0
2    6.5
3    4.5
4    3.0
5    2.0
6    4.5
dtype: float64

也可以根据值在原数据中出现的顺序给出排名:

In [110]: obj.rank(method='first')
Out[110]:
0    6.0
1    1.0
2    7.0
3    4.0
4    3.0
5    2.0
6    5.0
dtype: float64

这里,条目 0 和 2 没有使用平均排名 6.5,它们被设成了 6 和 7,因为数据中标签 0 位于标签 2 的前面。

你也可以按降序进行排名:

In [111]: obj.rank(ascending=False, method='max')
Out[111]:
0    2.0
1    7.0
2    2.0
3    4.0
4    5.0
5    6.0
6    4.0
dtype: float64

表5-6 列出了所有用于破坏平级关系的method选项。 DataFrame 可以在行或列上计算排名:

In [112]: frame = pd.DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1],
     ...:
     ...:    .....:                       'c': [-2, 5, 8, -2.5]})
     ...:

In [113]: frame
Out[113]:
   a    b    c
0  0  4.3 -2.0
1  1  7.0  5.0
2  0 -3.0  8.0
3  1  2.0 -2.5

In [114]: frame.rank(axis='columns')
Out[114]:
     a    b    c
0  2.0  3.0  1.0
1  1.0  3.0  2.0
2  2.0  1.0  3.0
3  2.0  3.0  1.0

Python for Data Analysis v2 | Notes_ Chapter_5 pandas 入门

带有重复标签的轴索引

直到目前为止,我所介绍的所有范例都有着唯一的轴标签(索引值)。虽然许多 pandas 函数(如 reindex)都要求标签唯一,但这并不是强制性的。我们来看看下面这个简单的带有重复索引值的 Series :

In [115]: obj = pd.Series(range(5), index=['a', 'a', 'b', 'b', 'c'])

In [116]: obj
Out[116]:
a    0
a    1
b    2
b    3
c    4
dtype: int32

索引的is_unique属性可以告诉你它的值是否是唯一的:

In [117]: obj.index.is_unique
Out[117]: False

对于带有重复值的索引,数据选取的行为将会有些不同。如果某个索引对应多个值,则返回一个 Series ;而对应单个值的,则返回一个标量值:

In [118]: obj['a']
Out[118]:
a    0
a    1
dtype: int32

In [119]: obj['c']
Out[119]: 4

这样会使代码变复杂,因为索引的输出类型会根据标签是否有重复发生变化。

对 DataFrame 的行进行索引时也是如此:

In [121]: df = pd.DataFrame(np.random.randn(4,3), index=['a','a','b','b'
     ...: ])

In [122]: df
Out[122]:
          0         1         2
a -0.846312  0.057249  1.071126
a -0.941121  0.889499  0.714435
b -1.692282 -2.131826  0.976986
b  1.394029  1.173624  0.072904

In [123]: df.loc['b']
Out[123]:
          0         1         2
b -1.692282 -2.131826  0.976986
b  1.394029  1.173624  0.072904

5.3 汇总和计算描述统计

pandas 对象拥有一组常用的数学和统计方法。它们大部分都属于约简和汇总统计,用于从 Series 中提取单个值(如summean)或从 DataFrame 的行或列中提取一个 Series 。跟对应的 NumPy 数组方法相比,它们都是基于没有缺失数据的假设而构建的。看一个简单的 DataFrame :

In [124]: df = pd.DataFrame([[1.4, np.nan], [7.1, -4.5],[np.nan, np.nan]
     ...: ,[0.75, -1.3]], index=['a', 'b', 'c', 'd'], columns=['one', 't
     ...: wo'])

In [125]: df
Out[125]:
    one  two
a  1.40  NaN
b  7.10 -4.5
c   NaN  NaN
d  0.75 -1.3

调用 DataFrame 的 sum 方法将会返回一个含有列的和的 Series :

In [126]: df.sum()
Out[126]:
one    9.25
two   -5.80
dtype: float64

传入axis='columns'axis=1将会按行进行求和运算:

In [127]: df.sum(axis=1)
Out[127]:
a    1.40
b    2.60
c     NaN
d   -0.55
dtype: float64

NA 值会自动被排除,除非整个切片(这里指的是行或列)都是 NA。通过skipna选项可以禁用该功能:

In [128]: df
Out[128]:
    one  two
a  1.40  NaN
b  7.10 -4.5
c   NaN  NaN
d  0.75 -1.3

In [129]: df.mean(axis='columns', skipna=False)
Out[129]:
a      NaN
b    1.300
c      NaN
d   -0.275
dtype: float64


In [130]: df.mean(axis='columns', skipna=True)
Out[130]:
a    1.400
b    1.300
c      NaN
d   -0.275
dtype: float64

表5-7 列出了这些约简方法的常用选项。

Python for Data Analysis v2 | Notes_ Chapter_5 pandas 入门
有些方法(如idxminidxmax)返回的是间接统计(比如达到最小值或最大值的索引):

In [131]: df.idxmax()
Out[131]:
one    b
two    d
dtype: object

另一些方法则是累计型的:

In [132]: df.cumsum()
Out[132]:
    one  two
a  1.40  NaN
b  8.50 -4.5
c   NaN  NaN
d  9.25 -5.8

还有一种方法,它既不是约简型也不是累计型。describe就是一个例子,它用于一次性产生多个汇总统计:

In [133]: df.describe()
Out[133]:
            one       two
count  3.000000  2.000000
mean   3.083333 -2.900000
std    3.493685  2.262742
min    0.750000 -4.500000
25%    1.075000 -3.700000
50%    1.400000 -2.900000
75%    4.250000 -2.100000
max    7.100000 -1.300000

对于非数值型数据,describe会产生另外一种汇总统计:

In [134]: obj = pd.Series(['a', 'a', 'b', 'c'] * 4)

In [135]: obj
Out[135]:
0     a
1     a
2     b
3     c
4     a
5     a
6     b
7     c
8     a
9     a
10    b
11    c
12    a
13    a
14    b
15    c
dtype: object

In [136]: obj.describe()
Out[136]:
count     16
unique     3
top        a
freq       8
dtype: object

表5-8列出了所有与描述统计相关的方法。

Python for Data Analysis v2 | Notes_ Chapter_5 pandas 入门

相关系数与协方差

有些汇总统计(如相关系数和协方差)是通过参数对计算出来的。我们来看几个 DataFrame ,它们的数据来自Yahoo!Finance的股票价格和成交量,使用的是 pandas -datareader包(可以用conda或pip安装):

import pandas_datareader.data as web
all_data = {ticker: web.get_data_yahoo(ticker)
            for ticker in ['AAPL', 'IBM', 'MSFT', 'GOOG']}

In [150]: all_data
Out[150]:
{'AAPL':                   Open        High         Low       Close   Adj Clos
e  \
 Date
 2009-12-31   30.447144   30.478571   30.080000   30.104286   20.379293
 2010-01-04   30.490000   30.642857   30.340000   30.572857   20.696493
 2010-01-05   30.657143   30.798571   30.464285   30.625713   20.732279
 2010-01-06   30.625713   30.747143   30.107143   30.138571   20.402502
 2010-01-07   30.250000   30.285715   29.864286   30.082857   20.364788

 ...                ...         ...         ...         ...         ...

 2018-04-06  170.970001  172.479996  168.199997  168.380005  168.380005
 2018-04-09  169.880005  173.089996  169.850006  170.050003  170.050003
 2018-04-10  173.000000  174.000000  171.529999  173.250000  173.250000

                Volume
 Date
 2009-12-31   88102700
 2010-01-04  123432400
 2010-01-05  150476200
 ...               ...

 2018-04-05   26933200
 2018-04-06   35005300
 2018-04-09   29017700
 2018-04-10   28408600

 [2082 rows x 6 columns],
 'GOOG':                    Open         High          Low        Close    Adj
 Close  \
 Date

 2009-12-31   310.356445   310.679321   307.986847   307.986847   307.986847

 2010-01-04   311.449310   312.721039   310.103088   311.349976   311.349976

 2010-01-05   311.563568   311.891449   308.761810   309.978882   309.978882

 2010-01-06   310.907837   310.907837   301.220856   302.164703   302.164703



 ...                 ...          ...          ...          ...          ...


 2018-04-04   993.409973  1028.718018   993.000000  1025.140015  1025.140015

 2018-04-05  1041.329956  1042.790039  1020.130981  1027.810059  1027.810059

 2018-04-06  1020.000000  1031.420044  1003.030029  1007.039978  1007.039978

 2018-04-09  1016.799988  1039.599976  1014.080017  1015.450012  1015.450012

 2018-04-10  1026.439941  1036.280029  1011.340027  1031.640015  1031.640015


               Volume
 Date
 2009-12-31   2455400
 2010-01-04   3937800
 2010-01-05   6048500
 2010-01-06   8009000
 2010-01-07  12912000
 2010-01-08   9509900

 ...              ...
 2018-04-03   2275100
 2018-04-04   2484700
 2018-04-05   1363000
 2018-04-06   1746400
 2018-04-09   1751600
 2018-04-10   1974500

 [2082 rows x 6 columns],
 'IBM':                   Open        High         Low       Close   Adj Close
  \
 Date
 2009-12-31  132.410004  132.850006  130.750000  130.899994  105.786804
 2010-01-04  131.179993  132.970001  130.850006  132.449997  107.039459
 2010-01-05  131.679993  131.850006  130.100006  130.850006  105.746422
 2010-01-06  130.679993  131.490005  129.809998  130.000000  105.059494
 2010-01-07  129.869995  130.250000  128.910004  129.550003  104.695824
 2010-01-08  129.070007  130.919998  129.050003  130.850006  105.746422

 ...                ...         ...         ...         ...         ...

 2018-04-03  150.800003  151.000000  148.300003  149.850006  149.850006
 2018-04-04  147.889999  154.470001  147.449997  154.119995  154.119995
 2018-04-05  154.440002  154.919998  153.339996  154.029999  154.029999
 2018-04-06  153.460007  153.949997  149.539993  150.570007  150.570007
 2018-04-09  151.800003  154.660004  151.740005  152.690002  152.690002
 2018-04-10  155.029999  156.600006  154.750000  155.389999  155.389999

               Volume
 Date
 2009-12-31   4223400
 2010-01-04   6155300
 2010-01-05   6841400
 2010-01-06   5605300
 2010-01-07   5840600
 2010-01-08   4197200
 2010-01-11   5730400
 2010-01-12   8081500

 ...              ...

 2018-03-28   3664800
 2018-03-29   3420000
 2018-04-02   5150400
 2018-04-03   4135700
 2018-04-04   4805300
 2018-04-05   3185400
 2018-04-06   3672900
 2018-04-09   4413200
 2018-04-10   3806400

 [2082 rows x 6 columns],
 'MSFT':                  Open       High        Low      Close  Adj Close
 Volume
 Date
 2009-12-31  30.980000  30.990000  30.480000  30.480000  24.651169   31929700
 2010-01-04  30.620001  31.100000  30.590000  30.950001  25.031296   38409100
 2010-01-05  30.850000  31.100000  30.639999  30.959999  25.039383   49749600
 2010-01-06  30.879999  31.080000  30.520000  30.770000  24.885712   58182400

 ...               ...        ...        ...        ...        ...        ...

 2018-04-05  92.440002  93.070000  91.400002  92.379997  92.379997   29771900
 2018-04-06  91.489998  92.459999  89.480003  90.230003  90.230003   38026000
 2018-04-09  91.040001  93.169998  90.620003  90.769997  90.769997   31533900
 2018-04-10  92.389999  93.279999  91.639999  92.879997  92.879997   26812000

 [2082 rows x 6 columns]}


In [148]: price = pd.DataFrame({ticker: data['Adj Close'] for ticker, data in
     ...:  all_data.items()})

In [149]: price
Out[149]:
                  AAPL         GOOG         IBM       MSFT
Date
2009-12-31   20.379293   307.986847  105.786804  24.651169
2010-01-04   20.696493   311.349976  107.039459  25.031296
2010-01-05   20.732279   309.978882  105.746422  25.039383
2010-01-06   20.402502   302.164703  105.059494  24.885712
2010-01-07   20.364788   295.130463  104.695824  24.626907
2010-01-08   20.500179   299.064880  105.746422  24.796747

...                ...          ...         ...        ...

2018-04-05  172.800003  1027.810059  154.029999  92.379997
2018-04-06  168.380005  1007.039978  150.570007  90.230003
2018-04-09  170.050003  1015.450012  152.690002  90.769997
2018-04-10  173.250000  1031.640015  155.389999  92.879997

[2082 rows x 4 columns]


In [151]: volume = pd.DataFrame({ticker: data['Volume']  for ticker, data in
     ...: all_data.items()})

In [152]: volume
Out[152]:
                 AAPL      GOOG       IBM       MSFT
Date
2009-12-31   88102700   2455400   4223400   31929700
2010-01-04  123432400   3937800   6155300   38409100
2010-01-05  150476200   6048500   6841400   49749600
...               ...       ...       ...        ...

2018-03-29   38398500   2726800   3420000   45867500
2018-04-02   37586800   2680400   5150400   48515400
2018-04-03   30278000   2275100   4135700   37213800
2018-04-04   34605500   2484700   4805300   35560000
2018-04-05   26933200   1363000   3185400   29771900
2018-04-06   35005300   1746400   3672900   38026000
2018-04-09   29017700   1751600   4413200   31533900
2018-04-10   28408600   1974500   3806400   26812000

[2082 rows x 4 columns]

我使用 pandas _datareader模块下载了一些股票数据:

  • 注意:此时Yahoo! Finance已经不存在了,因为2017年Yahoo!被Verizon收购了。参阅 pandas -datareader文档,可以学习最新的功能。

现在计算价格的百分数变化,时间序列的操作会在第11章介绍:

In [153]: returns = price.pct_change()

In [154]: returns.tail()
Out[154]:
                AAPL      GOOG       IBM      MSFT
Date
2018-04-04  0.019122  0.011575  0.028495  0.029205
2018-04-05  0.006934  0.002605 -0.000584  0.000541
2018-04-06 -0.025579 -0.020208 -0.022463 -0.023273
2018-04-09  0.009918  0.008351  0.014080  0.005985
2018-04-10  0.018818  0.015944  0.017683  0.023246

Series 的 corr 方法用于计算两个 Series 中重叠的、非 NA 的、按索引对齐的值的相关系数。与此类似,cov用于计算协方差:

In [155]: returns['MSFT'].corr(returns['IBM'])
Out[155]: 0.4832514652957767

In [156]: returns['MSFT'].cov(returns['IBM'])
Out[156]: 8.245627588528525e-05

因为 MSTF 是一个合理的 Python 属性,我们还可以用更简洁的语法选择列:

In [157]: returns.MSFT.corr(returns.IBM)
Out[157]: 0.4832514652957767

另一方面, DataFrame 的 corr 和cov方法将以 DataFrame 的形式分别返回完整的相关系数或协方差矩阵:

In [158]: returns.corr()
Out[158]:
          AAPL      GOOG       IBM      MSFT
AAPL  1.000000  0.427471  0.364269  0.415587
GOOG  0.427471  1.000000  0.398688  0.502238
IBM   0.364269  0.398688  1.000000  0.483251
MSFT  0.415587  0.502238  0.483251  1.000000

In [159]: returns.cov()
Out[159]:
          AAPL      GOOG       IBM      MSFT
AAPL  0.000259  0.000105  0.000070  0.000096
GOOG  0.000105  0.000233  0.000073  0.000110
IBM   0.000070  0.000073  0.000143  0.000082
MSFT  0.000096  0.000110  0.000082  0.000204

利用 DataFrame 的 corrwith方法,你可以计算其列或行跟另一个 Series 或 DataFrame 之间的相关系数。传入一个 Series 将会返回一个相关系数值 Series (针对各列进行计算):

In [160]: returns.corrwith(returns.IBM)
Out[160]:
AAPL    0.364269
GOOG    0.398688
IBM     1.000000
MSFT    0.483251
dtype: float64

传入一个 DataFrame 则会计算按列名配对的相关系数。这里,我计算百分比变化与成交量的相关系数:

In [161]: returns.corrwith(volume)
Out[161]:
AAPL   -0.065177
GOOG   -0.016318
IBM    -0.155764
MSFT   -0.084740
dtype: float64

传入axis='columns'即可按行进行计算。无论如何,在计算相关系数之前,所有的数据项都会按标签对齐。

唯一值、值计数以及成员资格

还有一类方法可以从一维 Series 的值中抽取信息。看下面的例子:

In [162]: obj = pd.Series(['c', 'a', 'd', 'a', 'a', 'b', 'b', 'c',
     ...:  'c'])

第一个函数是unique,它可以得到 Series 中的唯一值数组:

In [166]: uniques = obj.unique()

In [167]: uniques
Out[167]: array(['c', 'a', 'd', 'b'], dtype=object)

返回的唯一值是未排序的,如果需要的话,可以对结果再次进行排序(uniques.sort())。相似的,value_counts用于计算一个 Series 中各值出现的频率:

In [168]: uniques.sort()

In [169]: uniques
Out[169]: array(['a', 'b', 'c', 'd'], dtype=object)

In [170]: obj.value_counts()
Out[170]:
c    3
a    3
b    2
d    1
dtype: int64

为了便于查看,结果 Series 是按值频率降序排列的。value_counts还是一个* pandas 方法,可用于任何数组或序列:

In [171]: pd.value_counts(obj.values, sort=False)
Out[171]:
a    3
c    3
b    2
d    1
dtype: int64

isin用于判断矢量化集合的成员资格,可用于过滤 Series 中或 DataFrame 列中数据的子集:

In [172]: obj
Out[172]:
0    c
1    a
2    d
3    a
4    a
5    b
6    b
7    c
8    c
dtype: object

In [173]: mask = obj.isin(['b', 'c'])

In [174]: mask
Out[174]:
0     True
1    False
2    False
3    False
4    False
5     True
6     True
7     True
8     True
dtype: bool

In [175]: obj[mask]
Out[175]:
0    c
5    b
6    b
7    c
8    c
dtype: object

isin类似的是Index.get_indexer方法,它可以给你一个索引数组,从可能包含重复值的数组到另一个不同值的数组:

In [176]: to_match = pd.Series(['c', 'a', 'b', 'b', 'c', 'a'])

In [177]: unique_vals = pd.Series(['c', 'b', 'a'])

In [178]: pd.Index(unique_vals).get_indexer(to_match)
Out[178]: array([0, 2, 1, 1, 0, 2], dtype=int64)
  • unique_vals 中的 [‘c’, ‘b’, ‘a’] 分别用 0 1 2 表示,对应 to_match 中的['c', 'a', 'b', 'b', 'c', 'a'] 则为 [0, 2, 1, 1, 0, 2]

表5-9 给出了这几个方法的一些参考信息。

Python for Data Analysis v2 | Notes_ Chapter_5 pandas 入门

有时,你可能希望得到 DataFrame 中多个相关列的一张柱状图。例如:

In [179]: data = pd.DataFrame({'Qu1': [1, 3, 4, 3, 4],
     ...:    .....:                      'Qu2': [2, 3, 1, 2, 3],
     ...:    .....:                      'Qu3': [1, 5, 2, 4, 4]})
     ...:

In [180]:

In [180]: data
Out[180]:
   Qu1  Qu2  Qu3
0    1    2    1
1    3    3    5
2    4    1    2
3    3    2    4
4    4    3    4

pandas .value_counts传给该 DataFrame 的apply函数,就会出现:

In [181]: result = data.apply(pd.value_counts).fillna(0)

In [182]: result
Out[182]:
   Qu1  Qu2  Qu3
1  1.0  1.0  1.0
2  0.0  2.0  1.0
3  2.0  2.0  0.0
4  2.0  0.0  2.0
5  0.0  0.0  1.0
  • (1, Qu1)对应的 1.0 在 1 行中 Qu1 列中 出现过 1 次 ,同理 如 (3,Qu2 ) 位置的 2.0 代表 3 在 这一列中出现过 2 次

这里,结果中的行标签是所有列的唯一值。后面的频率值是每个列中这些值的相应计数。

5.4 总结

在下一章,我们将讨论用 pandas 读取(或加载)和写入数据集的工具。

之后,我们将更深入地研究使用 pandas 进行数据清洗、规整、分析和可视化工具。