Pandas对象简介

可以把Pandas看成增强版的NumPy结构化数组,行列都不再只是简单的整数索引,还可以带上标签。Pandas有三个基本数据结构:Series、DataFrame、Index。

Series

Pandas的Series对象是一个带索引数据构成的一维数组。可以用一个数组创建Series对象:

>>> import numpy as np
>>> import pandas as pd
>>> data = pd.Series([0.25, 0.5, 0.75, 1.0])
>>> data
0    0.25
1    0.50
2    0.75
3    1.00
dtype: float64

从上面的结果中,你会发现Series对象将一组数据和一组索引绑定在一起,我们可以通过Values属性和index属性获取数据。values属性返回的结果与NumPy数组类似:

>>> data.values
array([0.25, 0.5 , 0.75, 1.  ])

index属性返回的结果是一个类型为pd.Index的类数组对象。

>>> data.index
RangeIndex(start=0, stop=4, step=1)

和NumPy数组一样,数据可以通过Python的中括号索引标签获取:

>>> data[1]
0.5
>>> data[1:3]
1    0.50
2    0.75
dtype: float64

1.Series是通用的NumPy数组

到目前为止,你可能觉得Series对象和一维NumPy数组基本可以等价交换,但两者的本质差异其实是索引:NumPy数组通过隐式定义的整数索引获取数值,而Pandas的Series对象用一种显示定义的索引与数值关联。
显示索引的定义让Series对象拥有了更强的能力。例如,索引不再仅仅是整数,还可以是任何想要的类型。如果需要,完全可以用字符串定义索引:

>>> data = pd.Series([0.25, 0.5, 0.75, 1.0], index=['a', 'b', 'c', 'd'])
>>> data
a    0.25
b    0.50
c    0.75
d    1.00
dtype: float64

也可以使用不连续的索引:

>>> data = pd.Series([0.25, 0.5, 0.75, 1.0], index=[2, 3, 5, 7])
>>> data
2    0.25
3    0.50
5    0.75
7    1.00
dtype: float64

2.Series是特殊的字典

你可以把Pandas的Series对象看成一种特殊的Python字典。字典是一种将任意键映射到一组任意值的数据结构,而Series对象其实是一种将类型键映射到一组类型值得数据结构。

>>> population_dict = {'California': 38332521,'Texas': 26448193,'New York': 19651127,'Florida': 19552860,'Illinois': 12882135}
>>> population = pd.Series(population_dict)
>>> population
California    38332521
Texas         26448193
New York      19651127
Florida       19552860
Illinois      12882135
dtype: int64

和字典不同,Series对象还支持数组形式的操作,比如切片:

>>> population['California':'New York']
California    38332521
Texas         26448193
New York      19651127
dtype: int64

3.创建Series对象

我们已将见过几种创建Pandas的Series对象的方法,都是像这样的形式:

>>> pd.Series(data, index=index)

其中,index是一个可选参数,data参数支持各种数据类型。例如data可以是列表或者NumPy数组,这时index默认值为整数数列:

>>> pd.Series([2, 4, 6])
0    2
1    4
2    6
dtype: int64

data也可以是一个标量,创建Series对象会重复填充到每一个索引上:

>>> pd.Series(5)
0    5
dtype: int64
>>> pd.Series(5, index=[100, 200, 300])
100    5
200    5
300    5
dtype: int64

data还可以是一个字典:

>>> pd.Series({2:'a', 1:'b', 3:'c'})
2    a
1    b
3    c
dtype: object

DataFrame

和Series一样,DataFrame既可以作为一个通用性NumPy数组,也可以看做特殊的Python字典。

1.DataFrame是通用的NumPy数组

如果将Series类比作带灵活索引的一维数组,那么DataFrame就可以看做是一种既有灵活的行索引,又有灵活列索引的二维数组。

>>> population
California    38332521
Texas         26448193
New York      19651127
Florida       19552860
Illinois      12882135
dtype: int64
>>> area_dict = {'California': 423967, 'Texas': 695662, 'New York': 141297,
'Florida': 170312, 'Illinois': 149995}
>>> area = pd.Series(area_dict)
>>> area
California    423967
Texas         695662
New York      141297
Florida       170312
Illinois      149995
dtype: int64
>>> # 用一个字典创建一个包含这些信息的二维对象
>>> states = pd.DataFrame({'population':population, 'area':area})
>>> statespopulation    area
California    38332521  423967
Texas         26448193  695662
New York      19651127  141297
Florida       19552860  170312
Illinois      12882135  149995

DataFrame的index属性和columns属性

>>> states.index
Index(['California', 'Texas', 'New York', 'Florida', 'Illinois'], dtype='object')
>>> states.columns
Index(['population', 'area'], dtype='object')

DataFrame可以看做一种通用的NumPy二维数组,它的行列都可以通过索引获取。

2.DataFrame是特殊字典

与Series类似,我们也可以把DataFrame看成一种特殊的字典。字典是一个键映射一个值,而DataFrame是一列映射一个Series的数据。

>>> states['area']
California    423967
Texas         695662
New York      141297
Florida       170312
Illinois      149995
Name: area, dtype: int64

需要注意的是,在NumPy的二维数组里,data[0]返回第一行;而在DataFrame中,data[‘col0’]返回第一列。

3.创建DataFrame对象

(1)通过单个Series对象创建。DataFrame是一组Series对象的集合,可以用单个Series创建一个单列的DataFrame:

>>> population
California    38332521
Texas         26448193
New York      19651127
Florida       19552860
Illinois      12882135
dtype: int64
>>> pd.DataFrame(population, columns=['population'])population
California    38332521
Texas         26448193
New York      19651127
Florida       19552860
Illinois      12882135

(2)通过字典列表创建。任何元素是字典的列表都可以变成DataFrame:

>>> data = [{'a':i, 'b':2 * i}for i in range(3)]
>>> data
[{'a': 0, 'b': 0}, {'a': 1, 'b': 2}, {'a': 2, 'b': 4}]
>>> pd.DataFrame(data)a  b
0  0  0
1  1  2
2  2  4

即使字典中有些键不在,Pandas也会用缺失值NaN(不是数字)来表示:

>>> pd.DataFrame([{'a':1, 'b':2},{'b':3, 'c':4}])a  b    c
0  1.0  2  NaN
1  NaN  3  4.0

(3)通过Series对象字典创建。就像之前见过的那样,DataFrame也可以用一个由Series对象构成的字典创建:

>>> pd.DataFrame({'population':population, 'area':area})population    area
California    38332521  423967
Texas         26448193  695662
New York      19651127  141297
Florida       19552860  170312
Illinois      12882135  149995

(4)通过NumPy二维数组创建。假如有一个二维数组,就可以创建一个可以指定行列索引值得DataFrame。如果不指定行列索引值,那么行列默认都是整数索引值:

pd.DataFrame(np.random.rand(3, 2), columns=['foo', 'bar'], index=['a', 'b', 'c'])foo       bar
a  0.086317  0.664006
b  0.582919  0.211217
c  0.341268  0.032053

(5)通过NumPy结构化数组创建

以后补充

数据取值与选择

Series数据选择方法

1.将Series看做字典

>>> data = pd.Series([0.25, 0.5, 0.75, 1.0], index=['a', 'b', 'c', 'd'])
>>> data
a    0.25
b    0.50
c    0.75
d    1.00
dtype: float64
>>> data['b']
0.5

我们还可以用Python字典的表达式和方法来检测键、索引和值:

>>> 'a' in data
True
>>> data.keys()
Index(['a', 'b', 'c', 'd'], dtype='object')
>>> list(data.items())
[('a', 0.25), ('b', 0.5), ('c', 0.75), ('d', 1.0)]
>>> data.values()
Traceback (most recent call last):File "<pyshell#82>", line 1, in <module>data.values()
TypeError: 'numpy.ndarray' object is not callable
>>> data.values
array([0.25, 0.5 , 0.75, 1.  ])

Series对象可以用字典语法调整数据,就像你可以通过增加新的键扩展字典一样,你也可以通过增加新的索引扩展Series:

>>> data['e'] = 1.25
>>> data
a    0.25
b    0.50
c    0.75
d    1.00
e    1.25
dtype: float64

2.将Series看做一维数组

Series不仅有着和字典一样的接口,而且还具备和NumPy数组一样的数组数据选择功能,包括索引、掩码、花俏的索引等操作:

>>> data
a    0.25
b    0.50
c    0.75
d    1.00
e    1.25
dtype: float64
>>> # 将显示索引作为切片
>>> data['a':'c']
a    0.25
b    0.50
c    0.75
dtype: float64
>>> # 将隐式索引作为切片
>>> data[0:2]
a    0.25
b    0.50
dtype: float64
>>> # 掩码
>>> data[(data > 0.3) & (data < 0.8)]
b    0.50
c    0.75
dtype: float64
>>> # 花俏的索引
>>> data[['a', 'e']]
a    0.25
e    1.25
dtype: float64

在以上所有示例中,切片是绝大部分混乱之源。需要注意的是,当使用显示索引(即data[‘a’:‘c’])做切片时,结果包含最后一个索引;而当使用隐式索引(即data[0:2])做切片时,结果不包含最后一个索引

3.索引器:loc、iloc和ix

这些切片和取值的习惯用法经常会造成混乱。例如,如果你的Series是显示整数索引,那么data[1]这样的取值操作会使用显式索引,而data[1:3]这样的切片操作却会使用隐式索引。

>>> data = pd.Series(['a', 'b', 'c'], index=[1, 3, 5])
>>> data
1    a
3    b
5    c
dtype: object
>>> # 取值操作是显式索引
>>> data[1]
'a'
>>> # 切片操作是隐式索引
>>> data[1:2]
3    b
dtype: object

由于整数索引很容易造成混淆,所以Pandas提供了一些索引器(indexer)属性来作为取值的方法。
第一种索引器是loc属性,表示取值和切片都是显示的:

>>> data
1    a
3    b
5    c
dtype: object
>>> data.loc[1]
'a'
>>> data.loc[1:3]
1    a
3    b
dtype: object

第二种是iloc属性,表示取值和切片都是Python形式的隐式索引:

>>> data
1    a
3    b
5    c
dtype: object
>>> data.iloc[1]
'b'
>>> data.iloc[1:3]
3    b
5    c
dtype: object

第三种取值属性是ix,它是前两种索引器的混合形式,之后会提到。
Python代码设计原则之一是“显式优于隐式”。使用loc和iloc可以让代码更容易维护,可读性更高。特别是在处理整数索引的对象时,我强烈推荐使用着两种索引器,它们既可以让代码阅读和理解起来更加容易,也能避免因误用索引/切片而产生的小bug。

DataFrame数据选择方法

1.将DataFrame看做字典

>>> 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})
>>> dataarea       pop
California  423967  38332521
Texas       695662  26448193
New York    141297  19651127
Florida     170312  19552860
Illinois    149995  12882135

两个Series分别构成DataFrame的一列,可以通过对列名进行字典形式的取值来获取数据:

>>> data['area']
California    423967
Texas         695662
New York      141297
Florida       170312
Illinois      149995
Name: area, dtype: int64

对于字符串形式的列名,可以采用属性形式:

>>> data.area
California    423967
Texas         695662
New York      141297
Florida       170312
Illinois      149995
Name: area, dtype: int64

对同一个对象进行属性形式与字典形式获取的列数据,结果是相同的:

>>> data.area is data['area']
True

如果列名不是纯字符串,或者列名与DataFrame的方法同名,那么就不能用属性索引。
和Sries一样可以用字典形式的语法调整对象,比如增加一列:


>>> data['density'] = data['pop'] / data['area']
>>> dataarea       pop     density
California  423967  38332521   90.413926
Texas       695662  26448193   38.018740
New York    141297  19651127  139.076746
Florida     170312  19552860  114.806121
Illinois    149995  12882135   85.883763

2.将DataFrame看做二维数组

用values属性按行查看数组数据:

>>> dataarea       pop     density
California  423967  38332521   90.413926
Texas       695662  26448193   38.018740
New York    141297  19651127  139.076746
Florida     170312  19552860  114.806121
Illinois    149995  12882135   85.883763
>>> data.values
array([[4.23967000e+05, 3.83325210e+07, 9.04139261e+01],[6.95662000e+05, 2.64481930e+07, 3.80187404e+01],[1.41297000e+05, 1.96511270e+07, 1.39076746e+02],[1.70312000e+05, 1.95528600e+07, 1.14806121e+02],[1.49995000e+05, 1.28821350e+07, 8.58837628e+01]])

可以对DataFrame进行转置:

>>> dataarea       pop     density
California  423967  38332521   90.413926
Texas       695662  26448193   38.018740
New York    141297  19651127  139.076746
Florida     170312  19552860  114.806121
Illinois    149995  12882135   85.883763
>>> data.TCalifornia         Texas      New York       Florida      Illinois
area     4.239670e+05  6.956620e+05  1.412970e+05  1.703120e+05  1.499950e+05
pop      3.833252e+07  2.644819e+07  1.965113e+07  1.955286e+07  1.288214e+07
density  9.041393e+01  3.801874e+01  1.390767e+02  1.148061e+02  8.588376e+01

通过字典形式对列进行取值显然会限制我们把DataFrame作为NumPy数组可以获得的能力,尤其当我们在DataFrame数组中使用单个行索引获取一行数据时:

>>> data.values[0]
array([4.23967000e+05, 3.83325210e+07, 9.04139261e+01])
>>> # 而获取一列数据就需要向DataFrame传递单个列索引
>>> data['area']
California    423967
Texas         695662
New York      141297
Florida       170312
Illinois      149995
Name: area, dtype: int64

因此,在进行数组形式的取值时,我们需要用另一种方法——前面介绍过的Pandas索引器loc、iloc和lx了。通过iloc索引器,我们可以像对待NumPy数组一样索引Pandas的底层数组,DataFrame的行列标签会自动保留在结果中:

>>> dataarea       pop     density
California  423967  38332521   90.413926
Texas       695662  26448193   38.018740
New York    141297  19651127  139.076746
Florida     170312  19552860  114.806121
Illinois    149995  12882135   85.883763
>>> data.iloc[:3, :2]area       pop
California  423967  38332521
Texas       695662  26448193
New York    141297  19651127
>>> data.loc[:'Illinois', :'pop']area       pop
California  423967  38332521
Texas       695662  26448193
New York    141297  19651127
Florida     170312  19552860
Illinois    149995  12882135

使用ix索引器可以实现一种混合效果:

>>> data.ix[:3, :'pop']area       pop
California  423967  38332521
Texas       695662  26448193
New York    141297  19651127

任何用于处理NumPy形式数据的方法都可以用于这些索引器。例如,在loc中结合使用掩码和花俏的索引方法:

>>> data.loc[data.density > 100, ['pop', 'density']]pop     density
New York  19651127  139.076746
Florida   19552860  114.806121

任何一种取值方法都可以用于调整数据,这一点和NumPy相同:

>>> data.iloc[0, 2] = 90
>>> dataarea       pop     density
California  423967  38332521   90.000000
Texas       695662  26448193   38.018740
New York    141297  19651127  139.076746
Florida     170312  19552860  114.806121
Illinois    149995  12882135   85.883763

3.其他方法

这些方法虽然看着有点怪,但在实际中很好用。首先,如果对单个标签取值就选择列,而对多个标签用切片选择行:

>>> dataarea       pop     density
California  423967  38332521   90.000000
Texas       695662  26448193   38.018740
New York    141297  19651127  139.076746
Florida     170312  19552860  114.806121
Illinois    149995  12882135   85.883763
>>> data['area'] # 注意!单个标签只能是列标签。
California    423967
Texas         695662
New York      141297
Florida       170312
Illinois      149995
Name: area, dtype: int64
>>> data['Texas':'Illinois'] # 标签切片只能是行标签area       pop     density
Texas     695662  26448193   38.018740
New York  141297  19651127  139.076746
Florida   170312  19552860  114.806121
Illinois  149995  12882135   85.883763

掩码操作也可以直接对每一行进行过滤,而不使用loc索引器:

>>> data[data.density > 100]area       pop     density
New York  141297  19651127  139.076746
Florida   170312  19552860  114.806121
>>> data.density
California     90.000000
Texas          38.018740
New York      139.076746
Florida       114.806121
Illinois       85.883763
Name: density, dtype: float64

Pandas数值运算方法

通用函数:保留索引

因为Pandas是建立在NumPy基础之上的,所以NumPy的通用函数同样适用于Pands的Series和DataFrame对象。

>>> rng = np.random.RandomState(42)
>>> ser = pd.Series(rng.randint(0, 10, 4))
>>> ser
0    6
1    3
2    7
3    4
dtype: int64
>>> df = pd.DataFrame(rng.randint(0, 10, (3, 4)), columns=['A', 'B', 'C', 'C'])
>>> dfA  B  C  C
0  6  9  2  6
1  7  4  3  7
2  7  2  5  4
>>> # 如果对这两个对象的其中一个使用NumPy通用函数,生成的结果是另一个保留索引的Pandas对象
>>> np.exp(ser)
0     403.428793
1      20.085537
2    1096.633158
3      54.598150
dtype: float64
>>> np.sin(df * np.pi / 4)A             B         C             C
0 -1.000000  7.071068e-01  1.000000 -1.000000e+00
1 -0.707107  1.224647e-16  0.707107 -7.071068e-01
2 -0.707107  1.000000e+00 -0.707107  1.224647e-16

通用函数:索引对齐

当在两个Series或DataFrame对象上进行二元计算时,Pandas会在计算过程中对齐两个对象的索引,当你处理不完整的数据时,这一点非常方便。

1.Series索引对齐

>>> area = pd.Series({'Alaska': 1723337, 'Texas': 695662, 'California': 423967}, name='area')
>>> population = pd.Series({'California': 38332521, 'Texas': 26448193, 'New York': 19651127}, name='population')
>>> population / area
Alaska              NaN
California    90.413926
New York            NaN
Texas         38.018740
dtype: float64
>>> area.index | population.index
Index(['Alaska', 'California', 'New York', 'Texas'], dtype='object')
>>> area.index & population.index
Index(['Texas', 'California'], dtype='object')

对于缺失位置的数据,Pandas会用NaN填充,表示“此处无数”。这是Pandas表示缺失值的方法。

>>> A = pd.Series([2, 4, 6], index=[0, 1, 2])
>>> B = pd.Series([1, 3, 5], index=[1, 2, 3])
>>> A + B
0    NaN
1    5.0
2    9.0
3    NaN
dtype: float64

如果NaN值不是我们想要的结果,我们使用add函数,如A.add(B)等价于A + B,而且这个函数可以设置参数自定义A或B缺失的数据(np.add()函数没有fill_value参数):

>>> A.add(B, fill_value=0)
0    2.0
1    5.0
2    9.0
3    5.0
dtype: float64

这里的fill_value=0表示用0代替原本不存在的B[0]与A[0]=2做加法。

2.DataFrame索引对齐

>>> A = pd.DataFrame(rng.randint(0, 20, (2, 2)), columns=list('AB'))
>>> AA   B
0  1  11
1  5   1
>>> B = pd.DataFrame(rng.randint(0, 10, (3, 3)), columns=list('BAC'))
>>> BB  A  C
0  4  0  9
1  5  8  0
2  9  2  6
>>> A + BA     B   C
0   1.0  15.0 NaN
1  13.0   6.0 NaN
2   NaN   NaN NaN
>>> fill = A.stack().mean() # 对A取均值需要用stack将二维数组压缩成一维数组
>>> fill
4.5
>>> A.add(B, fill_value=fill)A     B     C
0   1.0  15.0  13.5
1  13.0   6.0   4.5
2   6.5  13.5  10.5

你会发现,两个对象的行列索引可以是不同顺序的,结果的索引会自动按照顺序排列。

>>> A = rng.randint(10, size=(3, 4))
>>> A
array([[3, 8, 2, 4],[2, 6, 4, 8],[6, 1, 3, 8]])
>>> A - A[0]
array([[ 0,  0,  0,  0],[-1, -2,  2,  4],[ 3, -7,  1,  4]])
>>> df = pd.DataFrame(A, columns=list('QRST'))
>>> df - df.iloc[0] # Pandas和NumPy默认都是按行计算的Q  R  S  T
0  0  0  0  0
1 -1 -2  2  4
2  3 -7  1  4
>>> df.subtract(df['R'], axis=0)Q  R  S  T
0 -5  0 -6 -4
1 -4  0 -2  2
2  5  0  2  7
>>> # 注意Pandas不像NumPy那样可以np.add()...
>>> pd.subtract(df, df['R'], axis=0)
Traceback (most recent call last):File "<pyshell#234>", line 1, in <module>pd.subtract(df, df['R'], axis=0)File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/pandas/__init__.py", line 214, in __getattr__raise AttributeError("module 'pandas' has no attribute '{}'".format(name))
AttributeError: module 'pandas' has no attribute 'subtract'

处理缺失值

选择处理缺失值的方法

在数据表或DataFrame中有很多识别缺失值的方法。一般情况下分为两种:一种方法是通过一个覆盖全局的掩码表示缺失值,另一种方法是用一个标签值表示缺失值。
在掩码方法中,掩码可能是一个与原数组维度相同的完整布尔类型数组,也可能是用一个比特(0或1)表示有缺失值的局部状态。
在标签方法中,标签值可能是具体的数据(例如用-9999表示缺失的整数),也可能是一些极少出现的形似。另外,标签值还可能是更全局的值,比如NaN(not a number)表示缺失的浮点数。

Pandas的缺失值

Pandas选择用标签方法表示缺失值,包括两种Python原有的缺失值:浮点类型的NaN值,以及Python的None对象。

1.None:Python对象类型的缺失值

由于None是一个Python对象,所以不能作为任何NumPy/Pandas数组类型的缺失值,只能用于’object‘数组类型(即由Python对象构成的数组):

>>> vals1 = np.array([1, None, 3, 4])
>>> vals1
array([1, None, 3, 4], dtype=object)

这里dtype=object表示NumPy认为由于这个数组是Python对象构成的,因此将其类型判断为object。
使用Python对象构成的数组就意味着如果你对一个包含None的数组进行累加操作,如sum()或者min(),那么通常会出现类型错误:

>>> vals1.sum()
Traceback (most recent call last):File "<pyshell#5>", line 1, in <module>vals1.sum()File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/numpy/core/_methods.py", line 38, in _sumreturn umr_sum(a, axis, dtype, out, keepdims, initial, where)
TypeError: unsupported operand type(s) for +: 'int' and 'NoneType'

也就是说,在Python中没有定义整数与None之间的加法运算。

2.NaN:数值类型的缺失值

另一种缺失值的标签是NaN(全程Not a Number,不是一个数字),是一种在任何系统中都兼容的特殊浮点数

>>> vals2 = np.array([1, np.nan, 3, 4])
>>> vals2.dtype
dtype('float64')

请注意,NumPy会为这个数组选择一个原生浮点类型,这意味着和之前的object类型数组不同,这个数组会被编译成C代码从而实现快速操作。你可以把NaN看作是一个数据类病毒——它将会与它接触过的数据同化。无论NaN进行何种操作,最终结果都是NaN:

>>> vals2 = np.array([1, np.nan, 3, 4])
>>> vals2.dtype
dtype('float64')
>>> 1 + np.nan
nan
>>> 0 * np.nan
nan
>>> vals2.sum(), vals2.min(), vals2.max()
(nan, nan, nan)

NumPy也提供了一些特殊的累计函数,他们可以忽略缺失值的影响:

>>> np.nansum(vals2), np.nanmin(vals2), np.nanmax(vals2)
(8.0, 1.0, 4.0)

谨记,NaN是一种特殊的浮点数,不是整数、字符串以及其他类型的数据类型。

处理缺失值

Pandas提供了一些方法来发现、剔除、替换数据结构中的缺失值,主要包括以下几种。
isnull()
创建一个布尔类型的掩码标签缺失值。
notnull()
与isnull()操作相反。
dropna()
返回一个剔除缺失值的数据。
fillna()
返回一个填充了缺失值的数据副本。

1.发现缺失值

Pandas数据结构有两种有效的方法可以发现缺失值:isnull()和notnull()。每种方法都返回布尔类型的掩码数据,例如:

>>> data = pd.Series([1, np.nan, 'hello', None])
>>> data.isnull()
0    False
1     True
2    False
3     True
dtype: bool

布尔掩码可以直接作为Series或DataFrame的索引使用:

>>> data[data.notnull()]
0        1
2    hello
dtype: object

2.剔除缺失值

>>> data
0        1
1      NaN
2    hello
3     None
dtype: object
>>> data.dropna()
0        1
2    hello
dtype: object

在DataFrame上使用他们时需要设置一些参数,例如:

>>> df = pd.DataFrame([[1, np.nan, 2], [2, 3, 5], [np.nan, 4, 6]])
>>> df0    1  2
0  1.0  NaN  2
1  2.0  3.0  5
2  NaN  4.0  6

我们没法从DataFrame中单独剔除一个值,要么是剔除缺失值所在的整行,要么是整列。
默认情况下,dropna()会剔除任何包含缺失值的整行数据:

>>> df.dropna()0    1  2
1  2.0  3.0  5

可以设置按不同的坐标轴剔除缺失值,比如axis=1(或axis=‘columns’)会剔除任何包含缺失值的整列数据:

>>> df.dropna(axis='columns')2
0  2
1  5
2  6

但是这么做也会把非缺失值一并剔除,因为可能有时候只需要剔除全部是缺失值的行或列,或者绝大多数是缺失值的行或列。这些需求可以通过设置how或thresh参数来满足,他们可以设置剔除行或列缺失值的数量阈值。
默认设置是how=‘any’,也就是说只要有缺失值就剔除整行或整列(通过axis设置坐标轴)。你还可以设置how=‘all’,这样就只会剔除全部是缺失值的行或列了:

>>> df[3] = np.nan
>>> df0    1  2   3
0  1.0  NaN  2 NaN
1  2.0  3.0  5 NaN
2  NaN  4.0  6 NaN
>>> df.dropna(axis='columns', how='all')0    1  2
0  1.0  NaN  2
1  2.0  3.0  5
2  NaN  4.0  6

还可以通过thresh参数设置行或列中非缺失值的最小数量,从而实现更加个性化的设置:

>>> df0    1  2   3
0  1.0  NaN  2 NaN
1  2.0  3.0  5 NaN
2  NaN  4.0  6 NaN
>>> df.dropna(axis='rows', thresh=3)0    1  2   3
1  2.0  3.0  5 NaN

也就是说至少有三个非缺失值的行才会被保留。

3.填充缺失值

有时候你可能并不像移除缺失值,而是想把他们替换成有效的数值。有效的值可能是像0、1、2那样单独的值,也可能是经过填充或转换得到的。虽然你可以通过isnull方法建立掩码来填充缺失值,但是Pandas为此专门提供了一个fillna()方法,他将返回填充了缺失值后的数组副本。

>>> data = pd.Series([1, np.nan, 2, None, 3], index=list('abcde'))
>>> data
a    1.0
b    NaN
c    2.0
d    NaN
e    3.0
dtype: float64
>>> # 用0来填充
>>> data.fillna(0)
a    1.0
b    0.0
c    2.0
d    0.0
e    3.0
dtype: float64

层级索引

到目前为止,我们接触的都是一维数据和二维数据,但我们也经常会遇到存储多维数据的需求,数据索引超过一两个键。因此Pandas引入了层级索引,也称为多级索引来配合多个不同等级(level)的一级索引一起使用,这样就可以将高维数组转换为类似一维Series和二维DataFrame对象的形式。

多级索引Series

让我们看看如何用一维Series对象表示二维数据。

1.Pandas多级索引

用元组表示索引是多级索引的基础,Pandas的MultiIndex类型提供了丰富的操作方法。我们可以用元组创建一个多级索引:

>>> index = [('California', 2000), ('California', 2010), ('New York', 2000), ('New York', 2010), ('Texas', 2000), ('Texas', 2010)]
>>> populations = [33871648, 37253956, 18976457, 19378102, 20851820, 25145561]
>>> index = pd.MultiIndex.from_tuples(index)
>>> index
MultiIndex([('California', 2000),('California', 2010),(  'New York', 2000),(  'New York', 2010),(     'Texas', 2000),(     'Texas', 2010)],)
>>> pop = pd.Series(populations, index=index)
>>> pop
California  2000    338716482010    37253956
New York    2000    189764572010    19378102
Texas       2000    208518202010    25145561
dtype: int64

其中前两列表示Series的多级索引值,第三列是数据。你会发现有些行仿佛缺失了第一列数据——这其实是多级索引的表现形式,每个空格与上面的索引相同。
现在可以直接用第二个索引获取2010年的全部数据,与Pandas的切片查询法一致:

>>> pop[:, 2010]
California    37253956
New York      19378102
Texas         25145561
dtype: int64

2.高维数据的多级索引

你可能已经注意到,我们完全可以用一个带行列索引的简单DataFrame代替前面的多级索引,其实Pandas已经实现了类似的功能。unstack()方法可以快速将一个多级索引的Series转换为普通索引的DataFrame。

>>> pop
California  2000    338716482010    37253956
New York    2000    189764572010    19378102
Texas       2000    208518202010    25145561
dtype: int64
>>> pop_df = pop.unstack()
>>> pop_df2000      2010
California  33871648  37253956
New York    18976457  19378102
Texas       20851820  25145561
>>> # stack()方法实现相反的效果
>>> pop_df.stack()
California  2000    338716482010    37253956
New York    2000    189764572010    19378102
Texas       2000    208518202010    25145561
dtype: int64

假如要增加一列数据,对于这种带有MultiIndex的对象,增加一列就像DataFrame的操作一样简单:

>>> pop
California  2000    338716482010    37253956
New York    2000    189764572010    19378102
Texas       2000    208518202010    25145561
dtype: int64
>>> pop_df = pd.DataFrame({'total':pop, 'under18':[9267089, 9284094,4687374, 4318033, 5906301, 6879014]})
>>> pop_dftotal  under18
California 2000  33871648  92670892010  37253956  9284094
New York   2000  18976457  46873742010  19378102  4318033
Texas      2000  20851820  59063012010  25145561  6879014

多级索引的创建方法

为Series或DataFrame创建多级索引最直接的方法就是将index参数设置为至少二维的索引数组:

>>> df = pd.DataFrame(np.random.rand(4, 2), index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]], columns=['data1', 'data2'])
>>> dfdata1     data2
a 1  0.697507  0.3353552  0.650819  0.391017
b 1  0.960825  0.2291142  0.040822  0.820750

MultiIndex的创建工作将在后台完成。
有时候显式的创建MultiIndex也是很有用的,下面来介绍一些创建方法。

1.显示地创建多级索引

通过一个有不同等级的若干简单数组组成的列表来构建MultiIndex:

>>> pd.MultiIndex.from_arrays([['a', 'a', 'b', 'b'], [1, 2, 1, 2]])
MultiIndex([('a', 1),('a', 2),('b', 1),('b', 2)],)

通过包含多个索引值的元组构成的列表来构建MultiIndex:

>>> pd.MultiIndex.from_tuples([('a', 1), ('a', 2), ('b', 1), ('b', 2)])
MultiIndex([('a', 1),('a', 2),('b', 1),('b', 2)],)

通过两个索引的笛卡尔积创建MultiIndex:

>>> pd.MultiIndex.from_product([['a', 'b'], [1, 2]])
MultiIndex([('a', 1),('a', 2),('b', 1),('b', 2)],)

直接提供levels(包含每个等级的索引值列表的列表)和codes(包含每个索引值标签列表的列表)创建MultiIndex:

>>> pd.MultiIndex(levels=[['a', 'b'], [1, 2]], codes=[[0, 0, 1, 1], [0, 1, 0, 1]])
MultiIndex([('a', 1),('a', 2),('b', 1),('b', 2)],)

在创建Series或DataFrame时,可以将这些对象作为index参数,或者通过reindex方法更新Series或DataFrame的索引。

2.多级索引的等级名称

给MultiIndex的的等级加上名称会为一些操作提供便利。你可以在前面任何一个MultiIndex构造器中通过names参数设置等级名称,也可以在创建之后通过索引的names属性来修改名称:

>>> pop.index.names = ['state', 'year']
>>> pop
state       year
California  2000    338716482010    37253956
Texas       2000    208518202010    25145561
New York    2000    189764572010    19378102
dtype: int64

再处理复杂数据时,为等级设置名称是管理多个索引的好办法。

3.多级列索引

>>> index = pd.MultiIndex.from_product([[2013, 2014], [1, 2]], names=['year', 'visit'])
>>> columns = pd.MultiIndex.from_product([['Bob', 'Guido', 'Sue'], ['HR', 'Temp']], names=['subject', 'type'])
>>> # 模拟数据
>>> data = np.round(np.random.randn(4, 6), 1)
>>> data[:, ::2] *= 10
>>> data += 37
>>> # 创建DataFrame
>>> health_data = pd.DataFrame(data, index=index, columns=columns)
>>> health_data
subject      Bob       Guido         Sue
type          HR  Temp    HR  Temp    HR  Temp
year visit
2013 1      50.0  35.4  17.0  37.6  50.0  36.02      37.0  37.2  39.0  35.1  37.0  35.2
2014 1      44.0  35.5  52.0  37.1  46.0  36.52      21.0  36.0  57.0  36.5  36.0  37.2

多级索引的取值与切片

1.Series多级索引

可以通过对多个级别索引值获取单个元素:

>>> pop
state       year
California  2000    338716482010    37253956
Texas       2000    208518202010    25145561
New York    2000    189764572010    19378102
dtype: int64
>>> pop['California', 2000]
33871648

MultiIndex也支持局部取值

>>> pop['California']
year
2000    33871648
2010    37253956
dtype: int64

类似的还有局部切片,不过要求MultiIndex是按顺序的。
下面的例子是通过布尔掩码选择数据:

>>> pop[pop > 22000000]
state       year
California  2000    338716482010    37253956
Texas       2010    25145561
dtype: int64

也可以用花俏的索引选择数据:

>>> pop[['California', 'Texas']]
state       year
California  2000    338716482010    37253956
Texas       2000    208518202010    25145561
dtype: int64

2.DataFrame多级索引

>>> health_data
subject      Bob       Guido         Sue
type          HR  Temp    HR  Temp    HR  Temp
year visit
2013 1      50.0  35.4  17.0  37.6  50.0  36.02      37.0  37.2  39.0  35.1  37.0  35.2
2014 1      44.0  35.5  52.0  37.1  46.0  36.52      21.0  36.0  57.0  36.5  36.0  37.2

由于DataFrame的基本索引是列索引,因此Series中多级索引的用法到了DataFrame中就应用到了列上。

>>> health_data['Guido', 'HR']
year  visit
2013  1        17.02        39.0
2014  1        52.02        57.0
Name: (Guido, HR), dtype: float64
>>> health_data.iloc[:2, :2]
subject      Bob
type          HR  Temp
year visit
2013 1      50.0  35.42      37.0  37.2

虽然这些索引器将多维数据当做二维数据处理,但在loc和iloc中可以传递多个层级的索引元组:

>>> health_data.loc[:, ('Bob', 'HR')]
year  visit
2013  1        50.02        37.0
2014  1        44.02        21.0
Name: (Bob, HR), dtype: float64

这种索引元组的用法不是很方便,有时还会导致错误。我们可以使用IndexSlice对象:

>>> health_data
subject      Bob       Guido         Sue
type          HR  Temp    HR  Temp    HR  Temp
year visit
2013 1      50.0  35.4  17.0  37.6  50.0  36.02      37.0  37.2  39.0  35.1  37.0  35.2
2014 1      44.0  35.5  52.0  37.1  46.0  36.52      21.0  36.0  57.0  36.5  36.0  37.2
>>> idx = pd.IndexSlice
>>> health_data.loc[idx[:, 1], idx[:, 'HR']]
subject      Bob Guido   Sue
type          HR    HR    HR
year visit
2013 1      50.0  17.0  50.0
2014 1      44.0  52.0  46.0

若想掌握它们,最好的办法就是使用它们!

多级索引行列转换

Pandas提供了许多操作,可以让数据在内容保持不变的同时,按照需要进行行列转换。之前我们演示过stack()和unstack()的用法,但其实还有很多合理控制层级行列索引的方法。

有序的索引和无序的索引

如果MultiIndex不是有序的索引,那么大多数切片操作都会失败。

>>> index = pd.MultiIndex.from_product([['a', 'c', 'b'], [1, 2]])
>>> data = pd.Series(np.random.rand(6), index=index)
>>> data.index.names = ['char', 'int']
>>> data
char  int
a     1      0.2549552      0.131735
c     1      0.0919562      0.552014
b     1      0.3724492      0.515396
dtype: float64
>>> try:data['a':'b']except KeyError as e:print(type(e))print(e)<class 'KeyError'>'Key length (1) was greater than MultiIndex lexsort depth (0)'

问题出在MultiIndex无序排列上。局部切片和许多相似的操作都要求MultiIndex的各级索引是有序的(按照地点顺序由A至Z)。为此Pandas提供了许多便捷的操作完成排序,如sort_index():

>>> data
char  int
a     1      0.2549552      0.131735
c     1      0.0919562      0.552014
b     1      0.3724492      0.515396
dtype: float64
>>> data = data.sort_index()
>>> data
char  int
a     1      0.2549552      0.131735
b     1      0.3724492      0.515396
c     1      0.0919562      0.552014
dtype: float64
>>> data['a':'b']
char  int
a     1      0.2549552      0.131735
b     1      0.3724492      0.515396
dtype: float64

多级索引的数据累计方法

前面我们己经介绍过一些Pandas自带的数据累计方法,比如mean()、sum()、和max()。而对于层级索引数据,可以设置参数level实现对数据子集的累计操作。

>>> health_data
subject      Bob       Guido         Sue
type          HR  Temp    HR  Temp    HR  Temp
year visit
2013 1      50.0  35.4  17.0  37.6  50.0  36.02      37.0  37.2  39.0  35.1  37.0  35.2
2014 1      44.0  35.5  52.0  37.1  46.0  36.52      21.0  36.0  57.0  36.5  36.0  37.2

如果你需要计算每一年各项指标的平均值,那么可以将参数level设置为索引year:

>>> data_mean = health_data.mean(level='year')
>>> data_mean
subject   Bob        Guido          Sue
type       HR   Temp    HR   Temp    HR   Temp
year
2013     43.5  36.30  28.0  36.35  43.5  35.60
2014     32.5  35.75  54.5  36.80  41.0  36.85

如果再设置axis参数,就可以对列索引进行类似的累计操作了:

>>> data_mean.mean(axis=1, level='type')
type         HR       Temp
year
2013  38.333333  36.083333
2014  42.666667  36.466667

这种语法其实就是GroupBy功能的快捷形式。

合并数据集:Concat与Append操作

将不同数据源合并是数据科学中最有趣的事情之一,这既包括将两个不同的数据集非常简单的拼接在一起,也包括用数据库那样的连接(join)与合并(merge)操作处理有重叠字段的数据集。
先用pd.cancat函数演示一个Series与DataFrame的简单合并操作。之后,我们将介绍Pandas中更复杂的merge和join内存数据合并操作。

知识回顾:NumPy数组的合并

合并Series和DataFrame与NumPy数组基本相同,后者通过np.concatenate函数即可完成。

>>> x = [1, 2, 3]
>>> y = [4, 5, 6]
>>> z = [7, 8, 9]
>>> np.concatenate([x, y, z])
array([1, 2, 3, 4, 5, 6, 7, 8, 9])
>>> x = [[1, 2], [3, 4]]
>>> np.concatenate([x, x], axis=1)
array([[1, 2, 1, 2],[3, 4, 3, 4]])

通过pd.concat实现简易合并

Pandas有一个pd.concat()函数与np.concatenate()语法类似,但是配置参数更多,功能也更强大:
pd.concat(objs, axis=0, join=‘outer’, join_axes=None, ignore_index=False, keys=None, levels=None, names=None, verify_integrity=False, copy=True)
pd.concat()可以简单地合并一维的Series或DataFrame对象:

>>> ser1 = pd.Series(['A', 'B', 'C'], index=[1, 2, 3])
>>> ser2 = pd.Series(['D', 'E', 'F'], index=[4, 5, 6])
>>> pd.concat([ser1, ser2])
1    A
2    B
3    C
4    D
5    E
6    F
dtype: object

也可以用来合并高维数据,如DataFrame:

>>> df1A   B
1  A1  B1
2  A2  B2
>>> df2A   B
3  A3  B3
4  A4  B4
>>> pd.concat([df1, df2])A   B
1  A1  B1
2  A2  B2
3  A3  B3
4  A4  B4

默认情况下,DataFrame和Series的合并都是逐行进行的(默认设置axis=0)。通过设置axis参数实现逐列合并:

>>> df3A   B
0  A0  B0
1  A1  B1
>>> df4C   D
0  C0  D0
1  C1  D1
>>> pd.concat([df3, df4], axis=1)A   B   C   D
0  A0  B0  C0  D0
1  A1  B1  C1  D1

这里也可以使用axis=‘col’,效果一样,但是axis='col’可能与之前的dropna(axis=‘columns’)搞混。

1.索引重复

np.concatenate与pd.concat最主要的差异之一就是Pandas在合并时会保留索引,即使索引是重复的。

>>> xA   B
0  A0  B0
1  A1  B1
>>> yA   B
0  A2  B2
1  A3  B3
>>> pd.concat([x, y])A   B
0  A0  B0
1  A1  B1
0  A2  B2
1  A3  B3

虽然DataFrame允许这么做,但结果并不是我们想要的。pd.concat提供了一些解决方法。
####(1)捕捉索引重复的错误
如果你想检测pd.concat合并的结果中是否出现了重复的索引,可以设置verify_integrity(验证完整性)。将参数设置为True,合并时若有索引重复就会触发异常。
####(2)忽略索引
有时索引无关紧要,那么合并时就可以忽略他们,可以通过设置ignore_index参数实现。如果参数设置为True,那么合并时会创建一个新的整数索引。

>>> xA   B
0  A0  B0
1  A1  B1
>>> yA   B
0  A2  B2
1  A3  B3
>>> pd.concat([x, y], ignore_index=True)A   B
0  A0  B0
1  A1  B1
2  A2  B2
3  A3  B3

####(3)增加多级索引
另一种处理索引重复的方法是通过keys参数为数据源设置多级索引标签,这样结果数据就会带上多级索引:

>>> pd.concat([x, y], keys=['x', 'y'])A   B
x 0  A0  B01  A1  B1
y 0  A2  B21  A3  B3

2.类似join的合并

前面介绍的简单实例都有一个共同的特点,那就是合并的DataFrame都是同样的列名。而在实际工作中,需要合并的数据往往带有不同的列名,而pd.concat提供了一些方法解决这类问题。

>>> df5A   B   C
1  A1  B1  C1
2  A2  B2  C2
>>> df6B   C   D
3  B3  C3  D3
4  B4  C4  D4
>>> pd.concat([df5, df6])A   B   C    D
1   A1  B1  C1  NaN
2   A2  B2  C2  NaN
3  NaN  B3  C3   D3
4  NaN  B4  C4   D4

默认合并方式是对所有输入列进行并集合并(join=‘outer’),当然也可以用join='inner’实现交集合并:

>>> pd.concat([df5, df6], join='inner')B   C
1  B1  C1
2  B2  C2
3  B3  C3
4  B4  C4

另一种合并方式是直接确定结果使用额列名,设置join_axes参数,里面是索引对象构成的列表(列表的列表)。将结果的列名设置为第一个输入的列名:

>>> pd.concat([df5, df6], join_axes=[df5.columns])A   B   C
1   A1  B1  C1
2   A2  B2  C2
3  NaN  B3  C3
4  NaN  B4  C4

3.append()方法

df1.append(df2)等效于pd.concat([df1, df2])。
需要注意的是,与Pytho列表中的append()方法不同,Pandas的append()不直接更新原有对象的值,而是为合并后的数据创建一个新对象。

合并数据集:合并与连接

Pandas的基本特性之一就是高性能的内存式数据连接(join)与合并(merge)操作。这些方法相比于concat更加强大。

数据连接的类型

pd.merge()函数实现了三种数据连接的类型:一对一、多对一和多对多。这三种数据连接类型都通过pd.merge()接口进行调用。

1.一对一连接

>>> df1employee        group
0      Bob   Accounting
1     Jake  Engineering
2     Lisa  Engineering
3      Sue           HR
>>> df2employee  hire_date
0     Lisa       2004
1      Bob       2008
2     Jake       2012
3      Sue       2014

若将这两个DataFrame合并成一个DataFrame,可以用pd.merge()函数实现:

>>> df3 = pd.merge(df1, df2)
>>> df3employee        group  hire_date
0      Bob   Accounting       2008
1     Jake  Engineering       2012
2     Lisa  Engineering       2004
3      Sue           HR       2014

pd.merge()方法会发现两个DataFrame都有“employee”列。并会自动以这列作为键进行连接。两个输入的合并结果是一个行的DataFrame。需要注意的是,共同列的位置可以是不一致的。例如这个例子中,虽然df1和df2中的“employee”列中各个数据值的索引不一样,但是pd.merge()会正确处理这个问题。另外,pd.merge()会默认丢弃原来的行索引。

2.多对一连接

多对一连接是指,在需要连接的两个例子中,有一列的值重复。通过多对一连接获得的结果DataFrame将会保留重复值:

>>> df3employee        group  hire_date
0      Bob   Accounting       2008
1     Jake  Engineering       2012
2     Lisa  Engineering       2004
3      Sue           HR       2014
>>> df4group supervisor
0   Accounting      Carly
1  Engineering      Guido
2           HR      Steve
>>> pd.merge(df3, df4)employee        group  hire_date supervisor
0      Bob   Accounting       2008      Carly
1     Jake  Engineering       2012      Guido
2     Lisa  Engineering       2004      Guido
3      Sue           HR       2014      Steve

在结果DataFrame中多了一个“supervisor”列,里面有些值会因为输入数据的对应关系而有所重复。

3.多对多连接

如果左右两个输入的共同列都包含重复值,那么合并结果就是一种多对多连接:

>>> df1employee        group
0      Bob   Accounting
1     Jake  Engineering
2     Lisa  Engineering
3      Sue           HR
>>> df5group        skills
0   Accounting          math
1   Accounting  spreadsheets
2  Engineering        coding
3  Engineering         linux
4           HR  spreadsheets
5           HR  organization
>>> pd.merge(df1, df5)employee        group        skills
0      Bob   Accounting          math
1      Bob   Accounting  spreadsheets
2     Jake  Engineering        coding
3     Jake  Engineering         linux
4     Lisa  Engineering        coding
5     Lisa  Engineering         linux
6      Sue           HR  spreadsheets
7      Sue           HR  organization

设置数据合并的键

我们已经见过了pd.merge()的默认行为:它会将两个输入的一个或多个共同列作为键进行合并。但由于两个输入要合并的列通常都不是同名的,因此pd.merge()提供了一些参数处理这个问题。

1.参数on用法

最简单的方法就是直接将参数on设置为一个列名字符串或者一个包含多列名称的列表:

>>> df1employee        group
0      Bob   Accounting
1     Jake  Engineering
2     Lisa  Engineering
3      Sue           HR
>>> df2employee  hire_date
0     Lisa       2004
1      Bob       2008
2     Jake       2012
3      Sue       2014
>>> pd.merge(df1, df2, on='employee')employee        group  hire_date
0      Bob   Accounting       2008
1     Jake  Engineering       2012
2     Lisa  Engineering       2004
3      Sue           HR       2014

这个参数只能在两个DataFrame有共同列名的时候才能使用。

2.left_on与right_on参数

有时你需要合并两个列名不相同数据集:

>>> df1employee        group
0      Bob   Accounting
1     Jake  Engineering
2     Lisa  Engineering
3      Sue           HR
>>> df3name  salary
0   Bob   70000
1  Jake   80000
2  Lisa  120000
3   Sue   90000
>>> pd.merge(df1, df3, left_on='employee', right_on='name')employee        group  name  salary
0      Bob   Accounting   Bob   70000
1     Jake  Engineering  Jake   80000
2     Lisa  Engineering  Lisa  120000
3      Sue           HR   Sue   90000
>>> # 获取的结果中会有一个多余的列,通过DataFrame的drop()方法去掉这一列
>>> pd.merge(df1, df3, left_on='employee', right_on='name').drop('name', axis=1)employee        group  salary
0      Bob   Accounting   70000
1     Jake  Engineering   80000
2     Lisa  Engineering  120000
3      Sue           HR   90000

3.left_index和right_index参数

除了合并列之外,你可能还需要合并索引:(set_index()使用新的一列作为索引)

>>> df1a = df1.set_index('employee')
>>> df2a = df2.set_index('employee')
>>> df1agroup
employee
Bob        Accounting
Jake      Engineering
Lisa      Engineering
Sue                HR
>>> df2ahire_date
employee
Lisa           2004
Bob            2008
Jake           2012
Sue            2014

你可以通过设置pd.merge()中的left_index和right_index参数将索引设置为键来实现合并:

>>> pd.merge(df1a, df2a, left_index=True, right_index=True)group  hire_date
employee
Bob        Accounting       2008
Jake      Engineering       2012
Lisa      Engineering       2004
Sue                HR       2014

为了方便,DataFrame实现了join()方法,它也可以按照索引进行数据合并:

>>> df1a.join(df2a)group  hire_date
employee
Bob        Accounting       2008
Jake      Engineering       2012
Lisa      Engineering       2004
Sue                HR       2014

如果想将索引与列混合使用,可以通过结合left_index和right_on或者结合left_on与right_index实现:

>>> df1agroup
employee
Bob        Accounting
Jake      Engineering
Lisa      Engineering
Sue                HR
>>> df3name  salary
0   Bob   70000
1  Jake   80000
2  Lisa  120000
3   Sue   90000
>>> pd.merge(df1a, df3, left_index=True, right_on='name')group  name  salary
0   Accounting   Bob   70000
1  Engineering  Jake   80000
2  Engineering  Lisa  120000
3           HR   Sue   90000

设置数据连接的集合操作规则

>>> df6name   food
0  Peter   fish
1   Paul  beans
2   Mary  bread
>>> df7name drink
0    Mary  wine
1  Joseph  beer
>>> pd.merge(df6, df7)name   food drink
0  Mary  bread  wine

我们合并两个数据集,在“name”列中只有一个共同的值:Mary。默认情况下,结果中只会包含两个输入集合的交集,这种连接方式被称为内连接(inner join)。我们可以用how参数设置连接方式,默认值为’inner’:

>>> pd.merge(df6, df7, how='inner')name   food drink
0  Mary  bread  wine

how参数支持的数据连接方式还有’outer’,‘left’,‘right’。**外连接(outer join)**返回两个输入列的并集,所有缺失值用NaN填充:

>>> pd.merge(df6, df7, how='outer')name   food drink
0   Peter   fish   NaN
1    Paul  beans   NaN
2    Mary  bread  wine
3  Joseph    NaN  beer

左连接(left join)和右连接(right join)返回的结果分别只包含左列和右列:

>>> df6name   food
0  Peter   fish
1   Paul  beans
2   Mary  bread
>>> df7name drink
0    Mary  wine
1  Joseph  beer
>>> pd.merge(df6, df7, how='left')name   food drink
0  Peter   fish   NaN
1   Paul  beans   NaN
2   Mary  bread  wine
>>> pd.merge(df6, df7, how='right')name   food drink
0    Mary  bread  wine
1  Joseph    NaN  beer

重复列名:suffixes参数

最后,你可能会遇见两个输入DataFrame有重名列的情况:

>>> df8name  rank
0   Bob     1
1  Jake     2
2  Lisa     3
3   Sue     4
>>> df9name  rank
0   Bob     3
1  Jake     1
2  Lisa     4
3   Sue     2
>>> pd.merge(df8, df9, on='name')name  rank_x  rank_y
0   Bob       1       3
1  Jake       2       1
2  Lisa       3       4
3   Sue       4       2

由于输出结果中有两个重复的列名,因此pd.merge函数会自动为他们添加后缀_x或_y,当然也可以通过suffixes参数自定义后缀名:

>>> pd.merge(df8, df9, on='name', suffixes=['_L', '_R'])name  rank_L  rank_R
0   Bob       1       3
1  Jake       2       1
2  Lisa       3       4
3   Sue       4       2

suffixes参数适用于任何连接方式,即使有三个及三个以上的重复列明也同样适用。

累计与分组

在较大的数据上进行分析时,一项基本的工作就是有效的数据累计:计算累计指标,如sum()、mean()、median()、min()和max(),其中每一个指标都呈现了大数据集的特征。在这一节中,我们探索Pandas的累计功能,从类似前面NumPy数组中的简单操作,到基于groupby实现的复杂操作。

行星数据

import seaborn as sns
>>> pla = sns.load_dataset('planets')
>>> pla.shape
(1035, 6)
>>> pla.head() # 显示前五行数据method  number  orbital_period   mass  distance  year
0  Radial Velocity       1         269.300   7.10     77.40  2006
1  Radial Velocity       1         874.774   2.21     56.95  2008
2  Radial Velocity       1         763.000   2.60     19.84  2011
3  Radial Velocity       1         326.030  19.40    110.62  2007
4  Radial Velocity       1         516.220  10.50    119.47  2009

seaborn包中包含了许多公开数据集,如果导入数据集过程中出现了如下的错误:

urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1076)>

这是因为seaborn数据集是从github网站导入的,似乎这个网站在http访问的时候安全证书出了一些问题,下面是解决办法:

import ssl
ssl._create_default_https_context = ssl._create_unverified_context

这两行代码可以让Python忽略安全证书的验证。
planets数据中包括了截至2014年已被发现的一千多课外行星数据资料。

Pandas的简单累计功能

与一维NumPy相同,Pandas的Series也会返回一个统计值:

>>> rng = np.random.RandomState(42)
>>> ser = pd.Series(rng.rand(5))
>>> ser
0    0.374540
1    0.950714
2    0.731994
3    0.598658
4    0.156019
dtype: float64
>>> ser.sum()
2.811925491708157
>>> ser.mean()
0.5623850983416314

DataFrame的累计函数默认对每列进行统计:


>>>
>>>
>>> df = pd.DataFrame({'A':rng.rand(5), 'B':rng.rand(5)})
>>> dfA         B
0  0.155995  0.020584
1  0.058084  0.969910
2  0.866176  0.832443
3  0.601115  0.212339
4  0.708073  0.181825
>>> df.mean()
A    0.477888
B    0.443420
dtype: float64
>>> df.mean(axis=1)
0    0.088290
1    0.513997
2    0.849309
3    0.406727
4    0.444949
dtype: float64

Pandas的Series和DataFrame支持NumPy中的所有累计函数(如下表):

另外,还有一个非常方便的describe()方法可以计算每一列的若干常用统计值,首先丢弃有缺失值的行:

>>> pla.head()method  number  orbital_period   mass  distance  year
0  Radial Velocity       1         269.300   7.10     77.40  2006
1  Radial Velocity       1         874.774   2.21     56.95  2008
2  Radial Velocity       1         763.000   2.60     19.84  2011
3  Radial Velocity       1         326.030  19.40    110.62  2007
4  Radial Velocity       1         516.220  10.50    119.47  2009
>>> pla.dropna().describe()number  orbital_period        mass    distance         year
count  498.00000      498.000000  498.000000  498.000000   498.000000
mean     1.73494      835.778671    2.509320   52.068213  2007.377510
std      1.17572     1469.128259    3.636274   46.596041     4.167284
min      1.00000        1.328300    0.003600    1.350000  1989.000000
25%      1.00000       38.272250    0.212500   24.497500  2005.000000
50%      1.00000      357.000000    1.245000   39.940000  2009.000000
75%      2.00000      999.600000    2.867500   59.332500  2011.000000
max      6.00000    17337.500000   25.000000  354.000000  2014.000000

25%, 50%和75%是对应的列数据的四分位数。四分位数(Quartile)是指在统计学中把所有数值由小到大排列并分成四等份,处于三个分割点位置的数值。
这是一种理解数据集所有统计属性的有效方法。例如,从年份year列中可以看出,1989年首次发现外行星,而且一半的已知行星都是在2010年及以后的年份被发现的。
Pandas的累计方法

DataFrame和Series对象支持以上所有方法。
但若想深入理解数据,仅仅依靠累计函数时远远不够的。数据累计的下一级别是groupby操作,他可以让你快速、有效的计算数据各子集的累计值。

GroupBy:分割、应用和组合

groupby(group by 分组)帮助我们对某些标签或索引的局部进行累计分析。

1.分割、应用和组合

  • 分割步骤将DataFrame按照指定的键分割成若干组。
  • 应用步骤对每个组应用函数,通常是累计、转换或过滤函数。
  • 组合步骤将每一组的结果合并成一个输出数组。


groupby操作的可视化过程

>>> df = pd.DataFrame({'key': ['A', 'B', 'C', 'A', 'B', 'C'], 'data': range(6)}, columns=['key', 'data'])
>>> dfkey  data
0   A     0
1   B     1
2   C     2
3   A     3
4   B     4
5   C     5

我们可以用DataFrame的groupby()方法进行绝大部分常见的分割-应用-组合操作,将需要分组的列名传进去即可:

>>> df.groupby('key')
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x129768c90>

返回的是一个DataFrameGroupBy对象。你可以将它看成一种特殊形式的DataFrame,里面隐藏着若干组数据,但是在没有应用累计函数之前不会计算。

>>> df.groupby('key').sum()data
key
A       3
B       5
C       7

2.GroupBy对象

GroupBy对象是一种非常灵活的抽象类型,你可以将它看做DataFrame的集合,在底层解决所有问题。
GroupBy中最重要的操作是aggregate、filter、transform和apply(累计、过滤、转换、应用)。后文详细介绍他们,现在先介绍一些GroupBy的基本操作方法:

(1)按列取值

>>> plamethod  number  orbital_period   mass  distance  year
0     Radial Velocity       1      269.300000   7.10     77.40  2006
1     Radial Velocity       1      874.774000   2.21     56.95  2008
2     Radial Velocity       1      763.000000   2.60     19.84  2011
3     Radial Velocity       1      326.030000  19.40    110.62  2007
4     Radial Velocity       1      516.220000  10.50    119.47  2009
...               ...     ...             ...    ...       ...   ...
1030          Transit       1        3.941507    NaN    172.00  2006
1031          Transit       1        2.615864    NaN    148.00  2007
1032          Transit       1        3.191524    NaN    174.00  2007
1033          Transit       1        4.125083    NaN    293.00  2008
1034          Transit       1        4.187757    NaN    260.00  2008[1035 rows x 6 columns]
>>> pla.head()method  number  orbital_period   mass  distance  year
0  Radial Velocity       1         269.300   7.10     77.40  2006
1  Radial Velocity       1         874.774   2.21     56.95  2008
2  Radial Velocity       1         763.000   2.60     19.84  2011
3  Radial Velocity       1         326.030  19.40    110.62  2007
4  Radial Velocity       1         516.220  10.50    119.47  2009
>>> pla.groupby('method')
<pandas.core.groupby.generic.DataFrameGroupBy object at 0x11c0e6910>
>>> pla.groupby('method')['orbital_period']
<pandas.core.groupby.generic.SeriesGroupBy object at 0x11dc427d0>

这里从原来的DataFrame中取某个列名作为一个Series组,和GroupBy对象一样,直到我们运行累计函数,才会开始运算。

>>> pla.groupby('method')['orbital_period'].median()
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)调用方法

借用Python类的魔力,可以让任何不由GroupBy对象直接实现的方法应用到每一组,例如你可以用DataFrame的describe()方法进行累计,对每一组数据进行描述性统计:

>>> pla.groupby('method')['year'].describe()count         mean  ...      75%     max
method                                             ...
Astrometry                       2.0  2011.500000  ...  2012.25  2013.0
Eclipse Timing Variations        9.0  2010.000000  ...  2011.00  2012.0
Imaging                         38.0  2009.131579  ...  2011.00  2013.0
Microlensing                    23.0  2009.782609  ...  2012.00  2013.0
Orbital Brightness Modulation    3.0  2011.666667  ...  2012.00  2013.0
Pulsar Timing                    5.0  1998.400000  ...  2003.00  2011.0
Pulsation Timing Variations      1.0  2007.000000  ...  2007.00  2007.0
Radial Velocity                553.0  2007.518987  ...  2011.00  2014.0
Transit                        397.0  2011.236776  ...  2013.00  2014.0
Transit Timing Variations        4.0  2012.500000  ...  2013.25  2014.0[10 rows x 8 columns]

这张表可以帮助我么对数据有更深刻的认识,例如大多数行星都是通过Radial Velocity和Transit方法发现的。

3.累计、过滤、转换和应用

为了之后的演示,使用下面这个DataFrame:

>>> 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'])
>>> dfkey  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()之类的简单函数,但是aggregate()可以支持更复杂的操作,比如字符串、函数或者函数列表,并且能一次性计算所有累加值:

>>> df.groupby('key').aggregate(['min', np.median, max]) # 演示了三种形参的样式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字典指定不同列需要累积的函数:

>>> df.groupby('key').aggregate({'data1':'min', 'data2':'max'})data1  data2
key
A        0      5
B        1      7
C        2      9

(2)过滤

过滤操作可以让你按照分组的属性丢弃若干数据。例如,我们可能只需要保留标准差超过某个阈值的组:

>>> def filter_func(x):return x['data2'].std() > 4>>> dfkey  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
>>> df.groupby('key').std()data1     data2
key
A    2.12132  1.414214
B    2.12132  4.949747
C    2.12132  4.242641
>>> 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,所以被丢弃了。
filter函数的形参是函数或者lambda表达式

(3)转换

累计操作返回的是对组内全数据量缩减过的结果。而转换操作会返回一个新的全量数据。其形状与原来的输入数据是一样的。常见的例子就是将**每一组(注意df由’key’列分为了ABC三组)**的样本数据减去各组的均值。来实现数据标准化:

>>> df.groupby('key').transform(lambda x: x - x.mean())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()方法让你在每个组上应用任意方法。下面的例子就是apply()方法将第一列数据以第二列的和为基数进行标准化:

>>> def norm_by_data2(x):x['data1'] /= x['data2'].sum()return x>>> dfkey  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
>>> df.groupby('key').apply(norm_by_data2)key     data1  data2
0   A  0.000000      5
1   B  0.142857      0
2   C  0.166667      3
3   A  0.375000      3
4   B  0.571429      7
5   C  0.416667      9

这里默认是先进行分割过程,例如B在apply()后的data1=0.14是1/(0+7)得来的

4.设置分割的键

前面的例子一直在用列名分割DataFrame。这只是众多分组操作中的一种,下面介绍更多的方法。
####(1)将列表、数组、Series或索引作为分组键。分组键可以是长度与DataFrame匹配的任意Series或列表,例如:

>>> L = [0, 1, 0, 1, 2, 0]
>>> dfkey  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
>>> df.groupby(L).sum()data1  data2
0      7     17
1      4      3
2      4      7

这里将列表L分割为三类:0, 1, 2。其中“0”类对应了df的索引为0, 2, 5的行数据。
####(2)用字典或Series将索引映射到分组名称:

>>> df2 = df.set_index('key')
>>> df2data1  data2
key
A        0      5
B        1      0
C        2      3
A        3      3
B        4      7
C        5      9
>>> mapping = {'A': 'vowel', 'B': 'consonant', 'C': 'consonant'}
>>> df2.groupby(mapping).sum()data1  data2
consonant     12     19
vowel          3      8

(3)任意Python函数

与前面的字典映射类似,你可以将任意Python函数传入groupby,函数映射到索引,然后新的分组输出:

>>> df2.groupby(str.lower).mean()data1  data2
a    1.5    4.0
b    2.5    3.5
c    3.5    6.0

groupby默认是用索引分组的,df2已将用’key’列作为索引了。

多个有效键构成的列表

任意之前有效的键都可以组合起来进行分组,从而返回一个多级索引的分组结果。

>>> df2.groupby([str.lower, mapping]).mean()data1  data2
a vowel        1.5    4.0
b consonant    2.5    3.5
c consonant    3.5    6.0

分组案例

运用上述知识,获取不同方法和不同年份发现的行星数量:

>>> decade = 10 * (pla['year'] // 10)
>>> decade = decade.astype(str) + 's' # astype将Pandas对象转换为指定的dtype,这里是转换为字符转类型。
>>> decade.name = 'decade'
>>>> pla.groupby(['method', decade])['number'].sum().unstack().fillna(0)
decade                         1980s  1990s  2000s  2010s
method
Astrometry                       0.0    0.0    0.0    2.0
Eclipse Timing Variations        0.0    0.0    5.0   10.0
Imaging                          0.0    0.0   29.0   21.0
Microlensing                     0.0    0.0   12.0   15.0
Orbital Brightness Modulation    0.0    0.0    0.0    5.0
Pulsar Timing                    0.0    9.0    1.0    1.0
Pulsation Timing Variations      0.0    0.0    1.0    0.0
Radial Velocity                  1.0   52.0  475.0  424.0
Transit                          0.0    0.0   64.0  712.0
Transit Timing Variations        0.0    0.0    0.0    9.0

数据透视表

我们已经介绍过GroupBy抽象类是如何探索数据集内部的关联性了。**数据透视表(pivot table)**是一种类似的操作方法。数据透视表将每一列数据作为输入,输出数据不断细分成多个维度累计信息的二维数据表。人们有时容易弄混数据透视表与GroupBy,但我觉得数据透视表更像是一种多维的GroupBy累计操作。也就是说,虽然你可以分割-应用-组合,但是分割与组合不是发生在一维索引上,而是在二维网格上(行列同时分组)。

演示数据透视表

>>> import seaborn as sns
>>> tit = sns.load_dataset('titanic')
>>> pd.set_option('display.max_columns', None) # 显示DataFrame对象的所有列
>>> tit.head()survived  pclass     sex   age  sibsp  parch     fare embarked  class  \
0         0       3    male  22.0      1      0   7.2500        S  Third
1         1       1  female  38.0      1      0  71.2833        C  First
2         1       3  female  26.0      0      0   7.9250        S  Third
3         1       1  female  35.0      1      0  53.1000        S  First
4         0       3    male  35.0      0      0   8.0500        S  Third   who  adult_male deck  embark_town alive  alone
0    man        True  NaN  Southampton    no  False
1  woman       False    C    Cherbourg   yes  False
2  woman       False  NaN  Southampton   yes   True
3  woman       False    C  Southampton   yes  False
4    man        True  NaN  Southampton    no   True

这份数据包含了惨遭厄运的每位乘客的大量信息,包括性别、年龄、船舱等级(class)和船票价格(fare paid)等。

手工制作数据透视表

将数据按照性别、最终生还状态或其他组合属性进行分组。这里先用上节的GroupBy实现。

>>> tit.groupby('sex')['survived'].mean()
sex
female    0.742038
male      0.188908
Name: survived, dtype: float64

这组数据告诉我们:有四分之三的女性被救,但只有五分之一的男性被救。

>>> tit.groupby(['sex', 'class'])['survived'].aggregate('mean').unstack()
class      First    Second     Third
sex
female  0.968085  0.921053  0.500000
male    0.368852  0.157407  0.135447

我们增加了一个分组“船舱等级”,然后通过行列转换索引操作将最里层的行索引转换成列索引,形成二维数组。可以看出有钱人的生还几率要更高。所以说努力学习,好好挣钱!嘻嘻。

数据透视表语法

用DataFrame的pivot_table实现的效果等同于上一节GroupBy的代码:

>>> tit.pivot_table('survived', index='sex', columns='class')
class      First    Second     Third
sex
female  0.968085  0.921053  0.500000
male    0.368852  0.157407  0.135447

与GroupBy方法相比,这行代码可读性更高,而且取得的结果也一样。

1.多级数据透视表

与GroupBy类似,数据透视表中的分组也可以通过各种参数指定多个等级。例如,我们可能想把“年龄”也加进去作为第三个维度,这就可以通过pd.cut函数将年龄进行分段:

>>> age = pd.cut(tit['age'], [0, 18, 80])
>>> age
0      (18.0, 80.0]
1      (18.0, 80.0]
2      (18.0, 80.0]
3      (18.0, 80.0]
4      (18.0, 80.0]...
886    (18.0, 80.0]
887    (18.0, 80.0]
888             NaN
889    (18.0, 80.0]
890    (18.0, 80.0]
Name: age, Length: 891, dtype: category
Categories (2, interval[int64]): [(0, 18] < (18, 80]]
>>> age.unique()
[(18.0, 80.0], NaN, (0.0, 18.0]]
Categories (2, interval[int64]): [(0, 18] < (18, 80]]
>>> tit.pivot_table('survived', ['sex', age], 'class')
class               First    Second     Third
sex    age
female (0, 18]   0.909091  1.000000  0.511628(18, 80]  0.972973  0.900000  0.423729
male   (0, 18]   0.800000  0.600000  0.215686(18, 80]  0.375000  0.071429  0.133663

对某一列也可以使用同样的策略——让我们用pd.qcut将船票按照计数项等分为两份:

>>> fare = pd.qcut(tit['fare'], 2)
>>> tit.pivot_table('survived', ['sex', age], [fare, 'class'])
fare            (-0.001, 14.454]                     (14.454, 512.329]  \
class                      First    Second     Third             First
sex    age
female (0, 18]               NaN  1.000000  0.714286          0.909091   (18, 80]              NaN  0.880000  0.444444          0.972973
male   (0, 18]               NaN  0.000000  0.260870          0.800000   (18, 80]              0.0  0.098039  0.125000          0.391304   fare
class              Second     Third
sex    age
female (0, 18]   1.000000  0.318182  (18, 80]  0.914286  0.391304
male   (0, 18]   0.818182  0.178571  (18, 80]  0.030303  0.192308

结果是一个四维累计数据表。

2.其他数据透视表选项

DataFrame的pivot_table完整形参表如下:

pivot_table(self, values=None, index=None, columns=None, aggfunc='mean', fill_value=None, margins=False, dropna=True, margins_name='All', observed=False)

我们已经介绍过前三个参数,来看看其他的参数。fill_value和dropna这两个参数用于处理缺失值。
aggfunc参数用于设置累计函数类型,默认值时均值(mean)。和GroupBy用法一样,累计函数可以用一些常见的字符串’sum’、‘mean’、‘count’、‘min’、'max’等也可以用np.sum()、sum()等表示。另外还可以通过字典为不同的列指定不同的累计函数:

>>> tit.pivot_table(index='sex', columns='class', aggfunc={'survived':sum, 'fare':'mean'})fare                       survived
class        First     Second      Third    First Second Third
sex
female  106.125798  21.970121  16.118810       91     70    72
male     67.226127  19.741782  12.661633       45     17    47

需要注意,这里忽略了一个参数values。当我们为aggfunc指定映射关系的时候,待透视的数值就已经确定了。
当需要计算每一组的总数时,可以通过margins参数设置:

>>> tit.pivot_table('survived', 'sex', 'class', margins=True)
class      First    Second     Third       All
sex
female  0.968085  0.921053  0.500000  0.742038
male    0.368852  0.157407  0.135447  0.188908
All     0.629630  0.472826  0.242363  0.383838

案例:美国人的生日

附上该实例所需csv的下载网址。

>>> bir = pd.read_csv('/Users/henruno/Downloads/PythonDataScienceHandbook-master/notebooks/data/births.csv')
>>> bir.head()year  month  day gender  births
0  1969      1  1.0      F    4046
1  1969      1  1.0      M    4440
2  1969      1  2.0      F    4454
3  1969      1  2.0      M    4548
4  1969      1  3.0      F    4548

先用一个数据透视表探索这份数据。增加一列表示不同年代,看看各年代的男女出生率:

>>> bir['decade'] = 10 * (bir['year'] // 10)
>>> bir.pivot_table('births', 'decade', 'gender', aggfunc='sum')
gender         F         M
decade
1960     1753634   1846572
1970    16263075  17121550
1980    18310351  19243452
1990    19479454  20420553
2000    18229309  19106428

我们马上就可以发现,光棍年年有!
我们可以用图片显示数据:

>>> import matplotlib.pyplot as plt
>>> sns.set() # 使用Seaborn风格
>>> bir.pivot_table('births', 'year', 'gender', aggfunc='sum').plot()
>>> plt.ylabel('total births per year')
>>> plt.show()


借助一个简单的数据透视表和plot()方法,我们马上就可以发现不同性别出生率的趋势。通过肉眼观察,过去50年间的男性出生率比女性出生率高50%。

向量化字符串操作

利用Python的一个优势就是字符串处理起来比较容易。在此基础上创建的Pandas同样提供了一系列向量化字符串操作。这一节我们学习如何用他们对一个从网络采集的杂乱无章的数据集进行局部清理。

Pandas字符串操作简介

>>> x = np.array([2, 3, 5, 7, 11, 13])
>>> x * 2
array([ 4,  6, 10, 14, 22, 26])
>>> # 向量化操作简化了纯数值的数组操作语法——我们不需要担心数组的长度或维度,只需要关心需要的操作,然而由于NumPy没有为字符串数组提供简单的接口,因此需要for循环解决问题
>>> data = ['peter', 'Paul', 'MARY', 'gUIDO']
>>> [s.capitalize() for s in data] # 首字母大写
['Peter', 'Paul', 'Mary', 'Guido']
>>> # 加入数据出现了缺失值,这样会引起异常
>>> data = ['peter', 'Paul', None, 'MARY', 'gUIDO']
>>> [s.capitalize() for s in data]
Traceback (most recent call last):File "<pyshell#123>", line 1, in <module>[s.capitalize() for s in data]File "<pyshell#123>", line 1, in <listcomp>[s.capitalize() for s in data]
AttributeError: 'NoneType' object has no attribute 'capitalize'

Pandas为包含字符串的Series和Index对象提供的str属性堪称两全其美的方法,它既可以满足向量化字符串操作的需求,又可以正确的处理缺失值:

>>> names = pd.Series(data)
>>> names
0    peter
1     Paul
2     None
3     MARY
4    gUIDO
dtype: object
>>> names.str.capitalize()
0    Peter
1     Paul
2     None
3     Mary
4    Guido
dtype: object

缺失值会被跳过。

Pandas字符串方法列表

>>> monte = pd.Series(['Graham Chapman', 'John Cleese', 'Terry Gilliam', 'Eric Idle', 'Terry Jones', 'Michael Palin'])
>>> monte
0    Graham Chapman
1       John Cleese
2     Terry Gilliam
3         Eric Idle
4       Terry Jones
5     Michael Palin
dtype: object

1.与Python字符串方法相似的方法

几乎所有Python内置的字符串方法都被复制到了Pandas的向量化字符串方法中。下面的表格列举了Pandas的str方法借鉴Python字符串方法的内容:

2.使用正则表达式的方法

还有一些支持正则表达式的方法可以用来处理每个字符串元素。下表中的内容是Pandas向量化字符串方法根据Python标准库的re模块函数实现的API:

>>> monte
0    Graham Chapman
1       John Cleese
2     Terry Gilliam
3         Eric Idle
4       Terry Jones
5     Michael Palin
dtype: object
>>> # 提取名字
>>> monte.str.extract('([A-Za-z]+)')0
0   Graham
1     John
2    Terry
3     Eric
4    Terry
5  Michael

3.其他字符串方法

(1)向量化字符串的取值与切片操作

get()和slice()操作可以从每个字符串数组中获取向量化元素。例如df.str.slice(0, 3)获取每个字符串数组的前三个字符。其等价于df.str[0:3]:

>>> monte.str[0:3]
0    Gra
1    Joh
2    Ter
3    Eri
4    Ter
5    Mic
dtype: object
>>> monte.str.split().str.get(-1)
0    Chapman
1     Cleese
2    Gilliam
3       Idle
4      Jones
5      Palin
dtype: object

(2)指标变量

>>> full_monte = pd.DataFrame({'name': monte, 'info': ['B|C|D', 'B|D', 'A|C', 'B|D', 'B|C', 'B|C|D']})
>>> full_montename   info
0  Graham Chapman  B|C|D
1     John Cleese    B|D
2   Terry Gilliam    A|C
3       Eric Idle    B|D
4     Terry Jones    B|C
5   Michael Palin  B|C|D
>>> full_monte['info'].str.get_dummies('|')A  B  C  D
0  0  1  1  1
1  0  1  0  1
2  1  0  1  0
3  0  1  0  1
4  0  1  1  0
5  0  1  1  1

Pandas超全总结相关推荐

  1. Pandas知识点超全总结

    Pandas知识点超全总结 一.数据结构 1.Series 1.创建 2.切片.修改 3.其他属性 2.DataFrame 1.创建 2.切片 3.增加.修改 4.删除 5.查看 二.读写数据 1.读 ...

  2. Python Re 模块超全解读!详细

    内行必看!Python Re 模块超全解读! 2019.08.08 18:59:45字数 953阅读 121 re模块下的函数 compile(pattern):创建模式对象 > import ...

  3. GitHub 超全机器学习工程师成长路线图,开源两日收获3700+Star!

    作者 | 琥珀 出品 | AI科技大本营(ID:rgznai100) 近日,一个在 GitHub 上开源即收获了 3700+ Star 的项目,引起了业界的注意.据介绍,该项目以 TensorFlow ...

  4. python excel库pip install_超全整理|Python 操作 Excel 库 xlwings 常用操作详解!

    原标题:超全整理|Python 操作 Excel 库 xlwings 常用操作详解! 来源:早起Python 作者:陈熹.刘早起 大家好,我是早起. 在之前的文章中我们曾详细的讲解了如何使用openp ...

  5. CVPR 2019超全论文合集新鲜出炉!| 资源帖

    整理 | 夕颜 出品 | AI科技大本营(ID: rgznai100) 实不相瞒,这是一个资源福利帖--CVPR 2019 接收论文超全合集! 此前关于 CVPR 2019 论文和合集出过不少,但是这 ...

  6. 超全的3D视觉数据集汇总

    作者:Tom Hardy Date: 2019-12-24 来源:超全的3D视觉数据集汇总

  7. 超全Python速查表登上GitHub热榜,标星4600+!(附链接)

    本文经AI新媒体量子位授权转载,转载请联系出处. 本文多资源,建议阅读5分钟. 本文为你分享一份超全Python速查表. 哪里不会,抄查哪里. GitHub上出现一份对Python用户非常友好的资源: ...

  8. 复旦邱锡鹏超全NLP预训练模型综述论文:两张图带你梳理完整脉络

    关注上方"深度学习技术前沿",选择"星标公众号", 资源干货,第一时间送达! 超全预训练语言模型概览,入门学习.搭建知识体系.找文献资料.找资源代码,这里有 N ...

  9. 超全!iOS 面试题汇总

    超全!iOS 面试题汇总 2015-10-20 CocoaChina 作者:Job_Yang 之前看了很多面试题,感觉要不是不够就是过于冗余,于是我将网上的一些面试题进行了删减和重排,现在分享给大家. ...

最新文章

  1. 邮件服务之Sendmail
  2. oracle号码段拆开,知道号段起止,如何选择该号段内的所有号码?
  3. Spring Security——认证失败时获取认证信息(用户名、密码、IP、SESSIONID)
  4. 【短语学习】盈余量分析(earned value analysis)
  5. 关机时无人照管更新正在运行_了解iOS13.1后,在决定更新
  6. Mysq数据库备份(win)
  7. 计算机二级34套word答案,全国计算机二级C选择题题库第34套
  8. java怎么使用floor_Java NavigableSet floor()用法及代码示例
  9. jquery表单对象属性选择器:enabled、:disabled、:checked、:selected
  10. http协议、cookie与session介绍
  11. Chaos 发布流体模拟王者 Phoenix 的5.0版本!
  12. 常见电子元器件的极性识别方法
  13. 计算机网络课制作双绞线实验,计算机网络实验报告(双绞线).doc
  14. operator int()用法
  15. python struct pack unpack
  16. 分裂的史莱姆(二进制)
  17. oracle绑定变量执行计划,绑定变量对执行计划的影响
  18. EasyClick IOS 自动化测试 使用前置准备
  19. MATLAB数字图像处理(一)
  20. AS400(系列)第一章 入门简介

热门文章

  1. Java 9 文章集锦
  2. 5G 应用及应用场景总结
  3. MoXing——华为云深度学习服务提供的网络模型开发API
  4. 14.11 基类与派生类关系的详细再探讨
  5. 深度剖析:Knative
  6. 安装psacct或acct程序包
  7. ClickHouse基础知识及与MySQL性能对比
  8. 【微课制作软件】Focusky教程 | 为什么 Focusky里的内容布局编辑不了?
  9. HTML中颜色的表示方法
  10. 企业级微服务构建-01搭建和使用Maven私有仓库(Nexus)-05仓库管理