Python数据科学学习笔记之——Pandas数据处理(一)

Pandas 数据处理

1、安装 Pandas

import pandas
print(pandas.__version__)		# 0.23.4

一般导入 Pandas 时我们使用别名 pd:

import pandas as pd

2、Pandas 对象简介

Pandas 的三个基本数据结构Series、DataFrame 和 Index

2.1、Pandas 的 Series 对象

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

data = pd.Series([0.25,0.5,0.75,1.0])
print(data)
'''
0    0.25
1    0.50
2    0.75
3    1.00
dtype: float64
'''

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

print(data.values)	# [0.25 0.5  0.75 1.  ]

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

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

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

print(data[1])		# 0.5
print(data[1:3])
'''
1    0.50
2    0.75
dtype: float64
'''
  1. Serise 是通用的 NumPy 数组

    Serise 对象与一维的 NumPy 数组基本可以等价交换,但是两者间的本质差异其实就是索引:NumPy 数组通过隐式定义的整数索引获取数值,而 Pandas 的 Series 对象用一种显式定义的索引与数组关联。

    显式索引的定义让 Series 对象拥有了更强的能力。例如,索引不再仅仅是整数,还可以是任意想要的类型。如果需要,完全可以是字符串:

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

    获取数值的方式与之前的一样:

    print(data['b'])		# 0.5
    

    也可以使用不按顺序或不连续的索引。

  2. Series 是特殊的字典

    字典是一种将任意键映射到一组任意值的数据结构,而 Series 对象其实是一种将类型键映射到一组类型值的数据结构。类型至关重要:就像 NumPy 数值背后特定类型的经过编译的代码使得它在某些操作上比普通的 Python 列表更加高效一样,Pandas Series 的类型信息使得它在某些操作上比 Python 的字典更高效。

    我们可以直接使用 Python 的字典创建一个 Series 对象,让 Series 对象与字典的类比更加清晰:

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

    典型的字典数值获取方式仍然有效:

    print(population['California'])		# 38332521
    

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

    print(population['California':'Florida'])
    '''
    California    38332521
    Texas         26448193
    New York      19651127
    Florida       19552860
    dtype: int64
    '''
    
  3. 创建 Series 对象

    一般的形式:

    pd.Series(data,index=index)
    

    其中,index 是一个可选参数,data 参数支持多种数据类型。

    例如,data 可以是列表或 NumPy 数组,这时 index 默认值为整数序列:

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

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

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

    data 还可以是一个字典,index 默认是排序的字典键:

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

    每一种形式都可以通过显式指定索引筛选需要的结果:

    print(pd.Series({2:'a',1:'b',3:'c'},index=[3,2]))
    '''
    3    c
    2    a
    dtype: object
    '''
    

    注意:Series 对象只会保留显式定义的键值对

2.2、Pandas 的 DataFrame 对象

DataFrame 既可以作为一个通用的 NumPy 数组,也可以看作是特殊的 Python 字典。

  1. DataFrame 是通用的 NumPy 数组

    如果把 Series 类比为带灵活索引的一维数组,那么 DataFrame 就可以看做是一种既有灵活的行索引,又有灵活列名的二维数组。你可以把 DataFrame 看成是有序排列的若干个 Series 对象,这里的 “排列” 指的是它们拥有共同的索引。

    area_dict = {'California':423967,'Texas':695662,'New York':141297,'Florida':170312,'Illinois':149995}
    area = pd.Series(area_dict)
    print(area)
    '''
    California    423967
    Texas         695662
    New York      141297
    Florida       170312
    Illinois      149995
    dtype: int64
    '''
    

    再结合之前创建的 population 的 Series 对象,用一个字典创建一个包含这些信息的二维对象:

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

    和 Series 对象一样,DataFrame 也有一个 index 属性可以获取行索引标签

    print(states.index)	
    # Index(['California', 'Texas', 'New York', 'Florida', 'Illinois'], dtype='object')
    

    另外,DataFrame 还有一个 columns 属性,是存放列标签的 Index 对象

    print(states.columns)		# Index(['population', 'area'], dtype='object')
    

    因此,DataFrame 的数值可以通过行列索引来获取,可以看做是一个二维数组。

  2. DataFrame 是特殊的字典

    字典是一个键映射到一个值,而 DataFrame 是一列映射一个 Series 的数据

    print(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:

      print(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)]
      print(pd.DataFrame(data))
      '''
         a  b
      0  0  0
      1  1  2
      2  2  4
      '''
      

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

      print(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 对象字典创建。

      print(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。如果不指定行列索引值,那么行列默认都是整数索引值:

      import numpy as np
      print(pd.DataFrame(np.random.rand(3,2),
                         columns=['foo','bar'],
                         index=['a','b','c']))
      '''
              foo       bar
      a  0.966568  0.012753
      b  0.143990  0.916020
      c  0.446891  0.402528
      '''
      
    5. 通过 NumPy 结构化数组创建。

      A = np.zeros(3,dtype=[('A','i8'),('B','f8')])
      print(A)		# [(0, 0.) (0, 0.) (0, 0.)]
      print(pd.DataFrame(A))
      '''
         A    B
      0  0  0.0
      1  0  0.0
      2  0  0.0
      '''
      

2.3、Pandas 的 Index 对象

Series 和 DataFrame 对象都使用便于引用和调整的显式索引。Pandas 的 Index 对象是一个很有趣的数据结构,可以将它看作是一个不可变数组或有序集合(实际上是一个多集,因为 Index 对象可能会包含重复值)。

ind = pd.Index([2,3,5,7,11])
print(ind)		# Int64Index([2, 3, 5, 7, 11], dtype='int64')
  1. 将 Index 看作不可变数组

    Index 对象的许多操作都像数组。例如,可以通过标准 Python 的取值方法获取数值,也可以通过切片获取数值。

    print(ind[1])		# 3
    print(ind[::2])		# Int64Index([2, 5, 11], dtype='int64')
    

    Index 对象还有许多与 NumPy 数组相似的属性:

    print(ind.size,ind.shape,ind.ndim,ind.dtype)		# 5 (5,) 1 int64
    

    Index 对象与 NumPy 数组之间的不同在于,Index 对象的索引是不可变的,也就是说不能通过通常的方式来调整。

  2. 将 Index 看作有序集合

    Pandas 对象被设计用于实现许多操作,如连接(join)数据集,其中涉及许多集合操作。Index 对象遵循 Python 标准库的集合(Set)数据结构的许多习惯用法,包括并集、交集、差集等:

    indA = pd.Index([1,3,5,7,9])
    indB = pd.Index([2,3,5,7,11])
    print(indA & indB)		# Int64Index([3, 5, 7], dtype='int64')
    print(indA | indB)		# Int64Index([1, 2, 3, 5, 7, 9, 11], dtype='int64')
    print(indA ^ indB)		# Int64Index([1, 2, 9, 11], dtype='int64')
    

3、数据取值与选择

3.1、Series 数据选择方法

Series 对象与一维 NumPy 数组和标准的 Python 字典在许多方面都一样。

  1. 将 Series 看作字典

    和字典一样,Series 对象提供了键值对的映射:

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

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

    print('a' in data)		# True
    print(data.keys())		# Index(['a', 'b', 'c', 'd'], dtype='object')
    print(list(data.items()))	# [('a', 0.25), ('b', 0.5), ('c', 0.75), ('d', 1.0)]
    

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

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

    Series 对象的可变性是一个非常方便的特性。

  2. 将 Series 看作一维数组

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

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

    注意:当使用显示索引(即 data[‘a’:’c’] 作切片时,结果包含最后一个索引);而当使用隐式索引(即 data[0:2])作切片时,结果不包含最后一个索引。

  3. 索引器:loc、iloc 和ix

    由于整数索引很容易造成混淆,所以 Pandas 提供了一些索引器属性来作为取值的方法。它不是 Series 对象的函数方法,而是暴露切片接口的属性。

    第一种索引器是 loc 属性,表示取值和切片都是显示的

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

    第三种取值属性是 ix,它是前两种索引器的混合形式。

3.2、DataFrame 数据选择方法

  1. 将 DataFrame 看作字典

    第一种类比是把 DataFrame 当作一个由若干个 Series 对象构成的字典。

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

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

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

    同样,也可以用属性形式选择纯字符串列名的数据:

    print(data.area)
    '''
    California    423967
    Texas         695662
    New York      141297
    Florida       170312
    Illinois      149995
    Name: area, dtype: int64
    '''
    

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

    print(data.area is data['area'])		# True
    

    显然,属性形式的数据选择方法很方便,但是它并不是通用的。如果列名不是纯字符串,或者列名与 DataFrame 的方法同名,那么就不能用属性索引。例如,DataFrame 有一个 pop() 方法,如果用 data.pop 就不会获取 ‘pop’ 列,而是显示为方法:

    print(data.pop is data['pop'])		# False
    

    和 Series 对象一样,还可以用字典形式的语法调整对象,如果要增加一列可以这样做:

    data['density'] = data['pop']/data['area']
    print(data)
    '''
                  area       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 看作二维数组

    可以把 DataFrame 看成是一个增强版的二维数组,用 values 属性按行查看数组数据

    print(data.values)
    '''
    [[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 上。例如,可以对 DataFrame 进行行列转置:

    print(data.T)
    '''
               California         Texas      ...            Florida      Illinois
    area     4.239670e+05  6.956620e+05      ...       1.703120e+05  1.499950e+05
    pop      3.833252e+07  2.644819e+07      ...       1.955286e+07  1.288214e+07
    density  9.041393e+01  3.801874e+01      ...       1.148061e+02  8.588376e+01
    
    [3 rows x 5 columns]
    '''
    

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

    print(data.values[0])	# [4.23967000e+05 3.83325210e+07 9.04139261e+01]
    

    而获取一列数据就需要向 DataFrame 传递单个列索引

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

    通过 iloc 索引器,我们就可以像对待 NumPy 数组一样索引 Pandas 的底层数组(Python 的隐式索引),DataFrame 的行列标签会自动保留在结果中:

    print(data.iloc[:3,:2])
    '''
                  area       pop
    California  423967  38332521
    Texas       695662  26448193
    New York    141297  19651127
    '''
    
    print(data.loc[:'Illinois',:'pop'])
    '''
                  area       pop
    California  423967  38332521
    Texas       695662  26448193
    New York    141297  19651127
    Florida     170312  19552860
    Illinois    149995  12882135
    '''
    

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

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

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

    data.iloc[0,2] = 90
    print(data)
    '''
                  area       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. 其他取值方法

    如果对单个标签取值就选择列,而对多个标签用切片就选择行:

    print(data['Florida':'Illinois'])
    '''
                area       pop     density
    Florida   170312  19552860  114.806121
    Illinois  149995  12882135   85.883763
    '''
    

    切片也可以不用索引,而直接用行数来实现:

    print(data[1:3])
    '''
                area       pop     density
    Texas     695662  26448193   38.018740
    New York  141297  19651127  139.076746
    '''
    

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

    print(data[data.density > 100])
    '''
                area       pop     density
    New York  141297  19651127  139.076746
    Florida   170312  19552860  114.806121
    '''
    

4、Pandas 数值运算方法

NumPy 的基本功能之一是快速对每个元素进行运算,既包括基本算术运算(加、减、乘、除),也包括更复杂的运算(三角函数、指数函数和对数函数等)。

但是 Pandas 也实现了一些高效技巧:对于一元运算(像函数与三角函数),这些通用函数将在输出结果中保留索引和列标签;而对于二元运算(如加法和乘法),Pandas 在传递通用函数时会自动对齐索引进行计算。这意味着,保留数据内容与组合不同来源的数据——两处在 NumPy 数组中都容易出错的地方——变成了 Pandas 的杀手锏。

4.1、通用函数:保留索引

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

rng = np.random.RandomState(42)
ser = pd.Series(rng.randint(0,10,4))
print(ser)
'''
0    6
1    3
2    7
3    4
dtype: int32
'''
df = pd.DataFrame(rng.randint(0,10,(3,4)),
                  columns=['A','B','C','D'])
print(df)
'''
   A  B  C  D
0  6  9  2  6
1  7  4  3  7
2  7  2  5  4
'''

如果对这两个对象的其中一个使用 NumPy 通用函数,生成的结果是另一个保留索引的 Pandas 对象:

print(np.exp(ser))
'''
0     403.428793
1      20.085537
2    1096.633158
3      54.598150
dtype: float64
'''

或者再做一个比较复杂的运算:

print(np.sin(df * np.pi / 4))
'''
          A             B         C             D
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
'''

4.2、通用函数:索引对齐

4.2.1、Series 索引对齐

假如你要整合两个数据源的数据:

area = pd.Series({'Alaska':123337,'Texas':695662,
                  'California':423967},name='area')
population = pd.Series({'California':38332521,'Texas':26448193,
                        'New York':19651127},name='population')
print(population / area)
'''
Alaska              NaN
California    90.413926
New York            NaN
Texas         38.018740
dtype: float64
'''

结果数组的索引是两个输入数组索引的并集。我们也可以用 Python 标准库的集合运算法则来获取这个索引:

print(area.index | population.index)	
# Index(['Alaska', 'California', 'New York', 'Texas'], dtype='object')

对于缺失位置的数据,Pandas 会用 NaN 填充,表示 “此处无数”。这是 Pandas 表示缺失值的方法。这种索引对齐方式是通过 Python 内置的集合运算规则来实现的,任何缺失值默认都用 NaN 填充:

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

如果用 NaN 值不是我们想要的结果,那么可以用适当的对象方法替代运算符。例如,A.add(B) 等价于 A + B,也可以设置参数自定义 A 或 B 缺失的数据:

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

4.2.2、DataFrame 索引对齐

在计算两个 DataFrame 时,类似的索引对齐规则也同样会出现在共同(并集)列中:

A = pd.DataFrame(rng.randint(0,20,(2,2)),
                 columns=list('AB'))
print(A)
'''
   A   B
0  1  11
1  5   1
'''
B = pd.DataFrame(rng.randint(0,10,(3,3)),
                 columns=list('ABC'))
print(B)
'''
   A  B  C
0  4  0  9
1  5  8  0
2  9  2  6
'''
print(A + B)
'''
      A     B   C
0   5.0  11.0 NaN
1  10.0   9.0 NaN
2   NaN   NaN NaN
'''

你会发现,两个对象的行列索引可以是不同顺序的,结果的索引会自动按顺序排列。在 Series 中,我们可以通过运算符方法的 fill_value 参数自定义缺失值。

这里,我们将用 A 中所有值的均值来填充缺失值(计算 A 的均值需要用 stack 将二维数组压缩成一维数组):

fill = A.stack().mean()
print(A.add(B,fill_value=fill()))
'''
      A     B   C
0   5.0  11.0 NaN
1  10.0   9.0 NaN
2   NaN   NaN NaN
'''

Python 运算符与相应的 Pandas 对应方法:

Python 运算符 Pandas 方法
+ add()
- sub()、subtract()
* mul()、multiply()
/ truediv()、div()、divide()
// floordiv()
% mod()
** pow()

4.2.3、通用函数:DataFrame 与 Series 的运算

DataFrame 与 Series 的运算规则,与 NumPy 中二维数组与一维数组的运算规则是一样的。一个例子,让一个二维数组减去自身的一行数据:

A = rng.randint(10,size=(3,4))
print(A)
'''
[[3 8 2 4]
 [2 6 4 8]
 [6 1 3 8]]
'''
print(A - A[0])
'''
[[ 0  0  0  0]
 [-1 -2  2  4]
 [ 3 -7  1  4]]
'''

根据 NumPy 的广播规则,让二维数组减自身的一行数据会按行计算。

在 Pandas 里默认也是按行计算的:

df = pd.DataFrame(A,columns=list('QRST'))
print(df - df.iloc[0])
'''
   Q  R  S  T
0  0  0  0  0
1 -1 -2  2  4
2  3 -7  1  4
'''

如果你想按列计算,那么就需要利用前面介绍过的运算符方法,通过 axis 参数设置:

print(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
'''

结果的索引都会自动对齐

halfrow = df.iloc[0,::2]
print(halfrow)
'''
Q    3
S    2
Name: 0, dtype: int32
'''
print(df - halfrow)
'''
     Q   R    S   T
0  0.0 NaN  0.0 NaN
1 -1.0 NaN  2.0 NaN
2  3.0 NaN  1.0 NaN
'''

5、处理缺失值

5.1、选择处理缺失值的方法

识别缺失值的方法一般分为两种:一种方法是通过一个覆盖全局的掩码表示缺失值,另一种方法是用一个标签值表示缺失值。

掩码方法中,掩码可能是一个与原数组维度相同的完整布尔类型数组也可能是用一个比特(0 或 1)表示有缺失值的局部状态

标签方法中,标签值可能是具体的数据也可能是一些极少出现的形式。另外,标签值还可能是更全局的值,比如用 NaN(不是一个数)表示缺失的浮点数,它是 IEEE 浮点数规范中指定的特殊字符。

使用单独的掩码数组会额外出现一个布尔类型数组,从而增加存储与计算的负担;而标签值方法缩小了可以被表示为有效值的范围,可能需要在 CPU 或 GPU 算术逻辑单元中增加额外的计算逻辑。

5.2、Pandas 的缺失值

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

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

    vals1 = np.array([1,None,3,4])
    print(vals1)		# [1 None 3 4]
    

    虽然这种类型在某些情景中非常有用,对数据的任何操作最终都会在 Python 层面完成但是在进行常见的快速操作时,这种类型比其他原生类型数组要消耗更多资源

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

    另一种缺失值的标签是 NaN(全称 Not a Number,不是一个数字),是一种按照 IEEE 浮点数标准设计、在任何系统中都兼容的特殊浮点数:

    vals2 = np.array([1,np.nan,3,4])
    print(vals2)		# [ 1. nan  3.  4.]
    

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

    print(1 + np.nan)		# nan
    print(0 * np.nan)		# nan
    

    虽然这些累计操作的结果定义是合理的(即不会抛出异常),但是并非总是有效的:

    print(vals2.sum())	# nan
    print(vals2.min())	# nan
    print(vals2.max())	# nan
    

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

    print(np.nansum(vals2))		# 8.0
    print(np.nanmin(vals2))		# 1.0
    print(np.nanmax(vals2))		# 4.0
    

    谨记,NaN 是一种特殊的浮点数。

  3. Pandas 中 NaN 与 None 的差异

    虽然 NaN 与 None 各有各的用处,但是 Pandas 把它们看成是可以等价交换的,在适当的时候会将两者进行替换:

    print(pd.Series([1,np.nan,2,None]))
    '''
    0    1.0
    1    NaN
    2    2.0
    3    NaN
    dtype: float64
    '''
    

    Pandas 会将没有标签值的数据类型自动转换为 NA。例如,当我们将整型数组中的一个值设置为 np.nan 时,这个值就会强制转换成浮点数缺失值 NA。

    x = pd.Series(range(2))
    print(x)
    '''
    0    0
    1    1
    dtype: int64
    '''
    x[0] = None
    print(x)
    '''
    0    NaN
    1    1.0
    dtype: float64
    '''
    

    Pandas 对 NA 缺失值进行强制转换的规则如下:

    类型 缺失值转换规则 NA 标签值
    floating 浮点型 无变化 np.nan
    object 对象类型 无变化 None 或 np.nan
    integer 整数类型 强制转换为 float64 np.nan
    boolean 布尔类型 强制转换为 float64 None 或 np.nan

    需要注意的是,Pandas 中字符串类型的数据通常是用 object 类型存储的。

5.3、处理缺失值

  1. 发现缺失值

    两种方法:isnull() 和 notnull()。每种方法返回布尔类型的掩码数据

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

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

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

    同样适用于 DataFrame。

  2. 剔除缺失值

    剔除缺失值:dropna();填充缺失值:fillna()。

    print(data.dropna())
    '''
    0        1
    2    hello
    dtype: object
    '''
    

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

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

    我们没法从 DataFrame 中单独剔除一个值,要么是剔除缺失值所在的行,要么是整列。

    默认情况下,dropna() 会剔除任何包含缺失值的整行数据

    print(df.dropna())
    '''
         0    1  2
    1  2.0  3.0  5
    '''
    

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

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

    但是这么做也会把非缺失值一并删除,因为可能有时候只需要剔除全部是缺失值的行或列,或者绝大多数是缺失值的行或列。这些需求可以通过设置 how 或 thresh 参数来满足,它们可以设置剔除行或列缺失值的数量阈值

    默认设置是 how = ‘any’也就是说只要有缺失值就剔除整行或整列(通过 axis 设置坐标轴)。你还可以设置 how = ‘all’,这样就只会剔除全部是缺失值的行或列了:

    df[3] = np.nan
    print(df)
    '''
         0    1  2   3
    0  1.0  NaN  2 NaN
    1  2.0  3.0  5 NaN
    2  NaN  4.0  6 NaN
    '''
    print(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 参数设置行或列中非缺失值的最小数量,从而实现更加个性化的配置。

    print(df.dropna(axis='rows',thresh=3))
    '''
         0    1  2   3
    1  2.0  3.0  5 NaN
    '''
    
  3. 填充缺失值

    fillna() 方法填充缺失值。

    data = pd.Series([1,np.nan,2,None,3],index=list('abcde'))
    print(data)
    '''
    a    1.0
    b    NaN
    c    2.0
    d    NaN
    e    3.0
    dtype: float64
    '''
    

    我们将用一个单独的值来填充缺失值,例如用 0:

    print(data.fillna(0))
    '''
    a    1.0
    b    0.0
    c    2.0
    d    0.0
    e    3.0
    dtype: float64
    '''
    

    可以用缺失值前面的有效值从前往后填充:

    #从前往后填充
    print(data.fillna(method='ffill'))
    '''
    a    1.0
    b    1.0
    c    2.0
    d    2.0
    e    3.0
    dtype: float64
    '''
    

    也可以用缺失值后面的有效值从后往前填充:

    #从后往前填充
    print(data.fillna(method='bfill'))
    '''
    a    1.0
    b    2.0
    c    2.0
    d    3.0
    e    3.0
    dtype: float64
    '''
    

    DataFrame 的操作方法与 Series 类似,只是在填充时需要设置坐标轴参数 axis:

    print(df)
    '''
         0    1  2   3
    0  1.0  NaN  2 NaN
    1  2.0  3.0  5 NaN
    2  NaN  4.0  6 NaN
    '''
    print(df.fillna(method='ffill',axis=1))
    '''
         0    1    2    3
    0  1.0  1.0  2.0  2.0
    1  2.0  3.0  5.0  5.0
    2  NaN  4.0  6.0  6.0
    '''
    

    需要注意的是,假如在从前往后填充时,需要填充的缺失值前面没有值,那么它就仍然是缺失值。


6、层级索引

存储多维数据的需求,数据索引超过一两个键。因此,Pandas 提供了 Panel 和 Panel4D 对象解决三维数据和四维数据。而在实践中,更直观的形式是通过层级索引,也被称为多级索引配合多个有不同等级的一级索引一起使用这样就可以将高维数组转换成类似一维 Series 和二维 DataFrame 对象的形式

6.1、多级索引 Series

如何使用一维的 Series 对象表示二维数组——用一系列包含特征与数值的数据点来简单演示。

  1. 笨方法

    两个不同年份的数据比较:

    index = [('California',2000),('California',2010),
             ('New York',2000),('New York',2010),
             ('Texas',2000),('Texas',2010)]
    population = [33871648,37253956,
                  18976457,19378102,
                  20851820,25145561]
    pop = pd.Series(population,index=index)
    print(pop)
    '''
    (California, 2000)    33871648
    (California, 2010)    37253956
    (New York, 2000)      18976457
    (New York, 2010)      19378102
    (Texas, 2000)         20851820
    (Texas, 2010)         25145561
    dtype: int64
    '''
    

    通过元组构成的多级索引,你可以直接在 Series 上取值或用切片查询数据:

    print(pop[('California',2010):('Texas',2000)])
    '''
    (California, 2010)    37253956
    (New York, 2000)      18976457
    (New York, 2010)      19378102
    (Texas, 2000)         20851820
    dtype: int64
    '''
    
  2. 好方法:Pandas 多级索引

    用元组表示索引其实是多级索引的基础,Pandas 的 MultiIndex 类型提供了更丰富的操作方法。

    index = pd.MultiIndex.from_tuples(index)
    print(index)
    '''
    MultiIndex(levels=[['California', 'New York', 'Texas'], [2000, 2010]],
               labels=[[0, 0, 1, 1, 2, 2], [0, 1, 0, 1, 0, 1]])
    '''
    

    你会发现 MultiIndex 里面有一个 levels 属性表示索引的等级——这样做可以将州名和年份作为每个数据点的不同标签

    如果将前面创建的 pop 的索引重置为 MultiIndex,就会看到层级索引:

    pop = pop.reindex(index)
    print(pop)
    '''
    California  2000    33871648
                2010    37253956
    New York    2000    18976457
                2010    19378102
    Texas       2000    20851820
                2010    25145561
    dtype: int64
    '''
    

    前两列表示 Series 的多级索引值,第三列是数据。

    现在可以直接使用第二个索引值获取 2010 年的全部数据,与 Pandas 的切片查询用法一致:

    print(pop[:,2010])
    '''
    California    37253956
    New York      19378102
    Texas         25145561
    dtype: int64
    '''
    

    结果是单索引的数组。

  3. 高维数据的多级索引

    我们其实完全可以用一个带行列索引的简单 DataFrame 代替前面的多级索引。unstack() 方法可以快速将一个多级索引的 Series 转化为普通索引的 DataFrame:

    pop_df = pop.unstack()
    print(pop_df)
    '''
                    2000      2010
    California  33871648  37253956
    New York    18976457  19378102
    Texas       20851820  25145561
    '''
    

    当然,也有 stack() 方法实现了相反的效果:

    print(pop_df.stack())
    '''
    California  2000    33871648
                2010    37253956
    New York    2000    18976457
                2010    19378102
    Texas       2000    20851820
                2010    25145561
    dtype: int64
    '''
    

    为什么研究层级索引:如果我们可以用含多级索引的一维 Series 数据表示二维数据,那么我们就可以用 Series 或 DataFrame 表示三维甚至更高维的数据。多级索引每增加一级,就表示数据增加一维,利用这一特性就可以轻松表示任意维度的数据了。

    pop_df = pd.DataFrame({'total':pop,
                           'under18':[9267089,9284094,
                                      4687374,4318033,
                                      5906301,6879014]})
    print(pop_df)
    '''
                        total  under18
    California 2000  33871648  9267089
               2010  37253956  9284094
    New York   2000  18976457  4687374
               2010  19378102  4318033
    Texas      2000  20851820  5906301
               2010  25145561  6879014
    '''
    

    上面介绍的通用函数和其他功能也同样适用于层级索引。

    f_u18 = pop_df['under18'] / pop_df['total']
    print(f_u18.unstack())
    '''
                    2000      2010
    California  0.273594  0.249211
    New York    0.247010  0.222831
    Texas       0.283251  0.273568
    '''
    

6.2、多级索引的创建方法

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

df = pd.DataFrame(np.random.rand(4,2),
                  index=[['a','a','b','b'],[1,2,1,2]],
                  columns=['data1','data2'])
print(df)
'''
        data1     data2
a 1  0.107431  0.859198
  2  0.627187  0.297819
b 1  0.668505  0.335400
  2  0.506130  0.572006
'''

MultiIndex 的创建工作将在后台进行。

同理,如果你把元组作为键的字典传递给 Pandas,Pandas 也会默认转换为 MultiIndex:

data = {('California',2000):33871648,
        ('California',2010):372539556,
        ('Texas',2000):20851820,
        ('Texas',2010):25145561,
        ('New York',2000):18976457,
        ('New York',2010):19378102}
print(pd.Series(data))
'''
California  2000     33871648
            2010    372539556
Texas       2000     20851820
            2010     25145561
New York    2000     18976457
            2010     19378102
dtype: int64
'''

但是有时候显式地创建 MultiIndex 也是很有用的。

  1. 显式地创建多级索引

    你可以用 pd.MultiIndex 中的类方法更加灵活地构建多级索引。例如,就像前面介绍的,你可以通过一个有不同等级的若干简单数组组成的列表来构建 MultiIndex:

    print(pd.MultiIndex.from_arrays([['a','a','b','b'],[1,2,1,2]]))
    '''
    MultiIndex(levels=[['a', 'b'], [1, 2]],
               labels=[[0, 0, 1, 1], [0, 1, 0, 1]])
    '''
    

    也可以通过包含多个索引值的元组构成的列表创建 MultiIndex:

    print(pd.MultiIndex.from_tuples([('a',1),('a',2),('b',1),('b',2)]))
    '''
    MultiIndex(levels=[['a', 'b'], [1, 2]],
               labels=[[0, 0, 1, 1], [0, 1, 0, 1]])
    '''
    

    还可以用两个索引的笛卡尔积创建 MultiIndex:

    print(pd.MultiIndex.from_product([['a','b'],[1,2]]))
    '''
    MultiIndex(levels=[['a', 'b'], [1, 2]],
               labels=[[0, 0, 1, 1], [0, 1, 0, 1]])
    '''
    

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

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

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

  2. 多级索引的等级名称

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

    pop.index.names = ['state','year']
    print(pop)
    '''
    state       year
    California  2000    33871648
                2010    37253956
    New York    2000    18976457
                2010    19378102
    Texas       2000    20851820
                2010    25145561
    dtype: int64
    '''
    
  3. 多级索引列

    每个 DataFrame 的行与列都是对称的,也就是说既然有多级行索引,那么同样可以有多级列索引。

    #多级行列索引
    index = pd.MultiIndex.from_product([[2013,2014],[1,2]],
                                       names=['year','visit'])
    colnums = 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=colnums)
    print(health_data)
    '''
    subject      Bob       Guido         Sue      
    type          HR  Temp    HR  Temp    HR  Temp
    year visit                                    
    2013 1      37.0  38.4  40.0  35.9  31.0  38.7
         2      33.0  37.3  40.0  37.8  33.0  37.7
    2014 1      34.0  36.4  33.0  37.4  31.0  37.4
         2      41.0  37.9  33.0  36.4  47.0  37.2
    '''
    

    多级行列索引的创建非常简单。上面创建了一个简易的四维数据,四个维度分别为被检查人的姓名、检查项目、检查年份和检查次数。可以在列索引的第一级查询姓名,从而获取包含一个人全部检查信息的 DataFrame:

    print(health_data['Guido'])
    '''
    type          HR  Temp
    year visit            
    2013 1      35.0  37.7
         2      30.0  37.0
    2014 1      41.0  38.6
         2      33.0  37.8
    '''
    

6.3、多级索引的取值与切片

  1. Series 多级索引

    print(pop)
    '''
    state       year
    California  2000    33871648
                2010    37253956
    New York    2000    18976457
                2010    19378102
    Texas       2000    20851820
                2010    25145561
    dtype: int64
    '''
    

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

    print(pop['California',2000])	# 33871648
    

    MultiIndex 也支持局部取值,即只取索引值的某一个层级。假如只取最高级的索引,获得的结果是一个新的 Series,未被选中的低层索引值会被保留:

    print(pop['California'])
    '''
    year
    2000    33871648
    2010    37253956
    dtype: int64
    '''
    

    类似的还有局部切片,不过要求 MultiIndex 是按顺序排列的:

    print(pop['California':'New York'])
    '''
    state       year
    California  2000    33871648
                2010    37253956
    New York    2000    18976457
                2010    19378102
    dtype: int64
    '''
    

    如果索引已经排好序,那么可以用较低层级的索引取值,第一层级的索引可以用空切片:

    print(pop[:,2000])
    '''
    state
    California    33871648
    New York      18976457
    Texas         20851820
    dtype: int64
    '''
    

    其他取值与数据选择的方法也都起作用。下面的例子是通过布尔掩码选择数据的:

    print(pop[pop > 22000000])
    '''
    state       year
    California  2000    33871648
                2010    37253956
    Texas       2010    25145561
    dtype: int64
    '''
    

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

    print(pop[['California','Texas']])
    '''
    state       year
    California  2000    33871648
                2010    37253956
    Texas       2000    20851820
                2010    25145561
    dtype: int64
    '''
    
  2. DataFrame 多级索引

    DataFrame 多级索引的用法与 Series 类似。

    print(health_data)
    '''
    subject      Bob       Guido         Sue      
    type          HR  Temp    HR  Temp    HR  Temp
    year visit                                    
    2013 1      42.0  37.2  37.0  35.3  33.0  36.5
         2      10.0  35.9  38.0  35.4   2.0  36.3
    2014 1      39.0  37.2  21.0  36.2  32.0  37.5
         2      37.0  35.8  35.0  36.7  54.0  36.8
    '''
    

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

    print(health_data['Guido','HR'])
    '''
    year  visit
    2013  1        43.0
          2        42.0
    2014  1        39.0
          2        47.0
    Name: (Guido, HR), dtype: float64
    '''
    

    与单索引类似,前面的 loc、iloc 和 ix 索引器都可以使用,例如:

    print(health_data.iloc[:2,:2])
    '''
    subject      Bob      
    type          HR  Temp
    year visit            
    2013 1      34.0  36.4
         2      37.0  36.2
    '''
    

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

    print(health_data.loc[:,('Bob','HR')])
    '''
    year  visit
    2013  1        43.0
          2        25.0
    2014  1        36.0
          2        51.0
    Name: (Bob, HR), dtype: float64
    '''
    

    这种索引元组的用法不是很方便,如果在元组中使用切片还会导致语法错误。

    虽然你可以用 Python 内置的 slice() 函数获取想要的切片,但是还有一种更好的办法,就是使用 IndexSlice 对象。

    idx = pd.IndexSlice
    print(health_data.loc[idx[:,1],idx[:,'HR']])
    '''
    subject      Bob Guido   Sue
    type          HR    HR    HR
    year visit                  
    2013 1      35.0  52.0  38.0
    2014 1      52.0  19.0  36.0
    '''
    

6.4、多级索引行列转换

  1. 有序的索引和无序的索引

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

    首先创建一个不按字典顺序排列的多级索引 Series:

    index = pd.MultiIndex.from_product([['a','c','b'],[1,2]])
    data = pd.Series(np.random.rand(6),index=index)
    data.index.names = ['char','int']
    print(data)
    '''
    char  int
    a     1      0.844849
          2      0.650447
    c     1      0.903680
          2      0.804958
    b     1      0.930337
          2      0.893623
    dtype: float64
    '''
    

    如果想对索引使用局部切片,那么错误就会出现:

    try:
        data['a':'b']
    except KeyError as e:
        print(type(e))
        print(e)
    '''
    
    'Key length (1) was greater than MultiIndex lexsort depth (0)'
    '''
    

    尽管从错误信息里面看不出具体细节,但问题是出在 MultiIndex 无序排列上。局部切片和许多其他相似的操作都要求 MultiIndex 的各级索引是有序的(即按照字典顺序由 A 至 Z)。为此,Pandas 提供了许多便捷的操作完成排序,如 sort_index() 和 sortlevel() 方法。

    data = data.sort_index()
    print(data)
    '''
    char  int
    a     1      0.087304
          2      0.197809
    b     1      0.387641
          2      0.016840
    c     1      0.360499
          2      0.237509
    dtype: float64
    '''
    

    索引排序后,局部切片就可以正常使用了:

    print(data['a':'b'])
    '''
    char  int
    a     1      0.088690
          2      0.718980
    b     1      0.323797
          2      0.798816
    dtype: float64
    '''
    
  2. 索引 stack 和 unstack

    我们可以将一个多级索引数据集转换成简单的二维形式,可以通过 level 参数设置转换的索引层级:

    print(pop.unstack(level=0))
    '''
    state  California  New York     Texas
    year                                 
    2000     33871648  18976457  20851820
    2010     37253956  19378102  25145561
    '''
    
    print(pop.unstack(level=1))
    '''
    year            2000      2010
    state                         
    California  33871648  37253956
    New York    18976457  19378102
    Texas       20851820  25145561
    '''
    

    unstack() 是 stack() 的逆操作,同时使用这两种方法让数据保持不变:

    print(pop.unstack().stack())
    '''
    state       year
    California  2000    33871648
                2010    37253956
    New York    2000    18976457
                2010    19378102
    Texas       2000    20851820
                2010    25145561
    dtype: int64
    '''
    
  3. 索引的设置与重置

    层级数据维度转换的另一种方法是行列标签转换,可以通过 reset_index 方法实现。如果在上面的人口数据 Series 中使用该方法,则会生成一个列标签中包含之前行索引标签 state 和 year 的 DataFrame。也可以用 name 属性为列设置名称:

    pop_flat = pop.reset_index(name='population')
    print(pop_flat)
    '''
            state  year  population
    0  California  2000    33871648
    1  California  2010    37253956
    2    New York  2000    18976457
    3    New York  2010    19378102
    4       Texas  2000    20851820
    5       Texas  2010    25145561
    '''
    

    在解决实际问题的时候,如果能将类似这样的原始输入数据的列直接转换成 MultiIndex,通常大有裨益。其实可以通过 DataFrame 的 set_index 方法实现,返回结果就会是一个带多级索引的 DataFrame:

    print(pop_flat.set_index(['state','year']))
    '''
                     population
    state      year            
    California 2000    33871648
               2010    37253956
    New York   2000    18976457
               2010    19378102
    Texas      2000    20851820
               2010    25145561
    '''
    

6.5、多级索引的数据累计方法

对于层级索引数据,可以设置参数 level 实现对数据子集的累计操作。

print(health_data)
'''
subject      Bob       Guido         Sue      
type          HR  Temp    HR  Temp    HR  Temp
year visit                                    
2013 1      28.0  36.5  30.0  37.3  34.0  36.7
     2      18.0  37.6  29.0  38.2  24.0  37.6
2014 1      17.0  36.2  48.0  37.0  40.0  35.5
     2      49.0  37.4  21.0  36.9  45.0  37.8
'''

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

data_mean = health_data.mean(level='year')
print(data_mean)
'''
subject   Bob        Guido          Sue       
type       HR   Temp    HR   Temp    HR   Temp
year                                          
2013     32.0  36.85  49.0  36.65  43.5  36.75
2014     40.5  36.85  48.5  37.55  48.0  37.45
'''

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

print(data_mean.mean(axis=1,level='type'))
'''
type         HR       Temp
year                      
2013  39.166667  36.966667
2014  42.666667  36.300000
'''

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

你可能感兴趣的:(Python数据科学,数据分析,python,机器学习)