首页 > 技术文章 > 利用Python进行数据分析-Pandas(第二部分)

lsyb-python 2019-11-29 17:03 原文

上一个章节中我们主要是介绍了pandas两种数据类型的具体属性,这个章节主要介绍操作Series和DataFrame中的数据的基本手段。

一、基本功能

1、重新索引

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

import pandas as pd

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

用该Series的reindex将会根据新索引进行重排。如果某个索引值当前不存在,就引入缺失值:

obj2 = obj.reindex(['a', 'b', 'c', 'd', 'e'])
print(obj2)
a   -5.3
b    7.2
c    3.6
d    4.5
e    NaN
dtype: float64

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

obj3 = pd.Series(['blue', 'purple', 'yellow'], index=[0, 2, 4])
print(obj3)
0      blue
2    purple
4    yellow
dtype: object
print(obj3.reindex(range(6), method='ffill'))
0      blue
1      blue
2    purple
3    purple
4    yellow
5    yellow
dtype: object

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

frame = pd.DataFrame(np.arange(9).reshape(3, 3), index=['a', 'c', 'd'], columns=['Ohio', 'Texas', 'California'])
print(frame)
   Ohio  Texas  California
a     0      1           2
c     3      4           5
d     6      7           8
frame2= frame.reindex(['a', 'b', 'c', 'd'])
print(frame2)
   Ohio  Texas  California
a   0.0    1.0         2.0
b   NaN    NaN         NaN
c   3.0    4.0         5.0
d   6.0    7.0         8.0

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

states = ['Texas', 'Utah', 'California']
print(frame.reindex(columns=states))
   Texas  Utah  California
a      1   NaN           2
c      4   NaN           5
d      7   NaN           8

如下列出了reindex函数的各参数及说明:

 

  • index                                                  用作索引的新序列。既可以是index实例,也可以是其他序列类型的Python数据结构。Index会被完全使用,就像没有任何复制一样;
  • method                                                    插值(填充)方式;
  • fill_value                                                  在重新索引的过程中,需要引入缺失值时使用的替代值;
  • limit                                                          前向或后向填充时的最大填充量;
  • tolerance                                                  向前后向后填充时,填充不准确匹配项的最大间距(绝对值距离)
  • level                                                         在MultiIndex的指定级别上匹配简单索引,否则选取其子集;
  • copy                                                         默认为True,无论如何都复制;如果为False,则新旧相等就不复制。

2、丢弃指定轴上的项

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

import pandas as pd
import numpy as np

obj = pd.Series(np.arange(5.), index=['a', 'b', 'c', 'd', 'e'])
print(obj)
a    0.0
b    1.0
c    2.0
d    3.0
e    4.0
dtype: float64

删除索引C,并生成新的对象:

new_obj = obj.drop('c')
print(new_obj)
a    0.0
b    1.0
d    3.0
e    4.0
dtype: float64

删除列表索引,并生成新的对象:

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

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

import pandas as pd
import numpy as np
data = pd.DataFrame(np.arange(16).reshape(4, 4), index=['Ohio', 'Colorado', 'Utah', 'NewYork'], columns=['one', 'two', 'three', 'four'])
print(data)
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11
NewYork    12   13     14    15

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

print(data.drop(['Colorado', 'Ohio']))
         one  two  three  four
Utah       8    9     10    11
NewYork   12   13     14    15

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

print(data.drop('two', axis=1))
          one  three  four
Ohio        0      2     3
Colorado    4      6     7
Utah        8     10    11
NewYork    12     14    15
print(data.drop(['two', 'four'], axis='columns'))
          one  three
Ohio        0      2
Colorado    4      6
Utah        8     10
NewYork    12     14

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

obj.drop('c', inplace=True)
print(obj)
a    0.0
b    1.0
d    3.0
e    4.0
dtype: float64

因此小心使用inplace,它会销毁所有被删除的数据。

3、索引、选取和过滤 

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

import pandas as pd
import numpy as np
obj = pd.Series(np.arange(4.), index=['a', 'b', 'c', 'd'])
print(obj)
a    0.0
b    1.0
c    2.0
d    3.0
dtype: float64

获得索引为b的数据:

print(obj['b'])
1.0

获得自动索引为1的数据:

print(obj[1])
1.0

获得自动索引列表[2:4]的数据:

print(obj[2:4])
c    2.0
d    3.0
dtype: float64

也可以使用自定义索引:

print(obj[['b', 'a', 'd']])
b    1.0
a    0.0
d    3.0
dtype: float64

使用自动索引:

print(obj[[1, 3]])
b    1.0
d    3.0
dtype: float64

可以使用判断切片:

print(obj[obj < 2])
a    0.0
b    1.0
dtype: float64

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

print(obj['b':'d'])
b    1.0
c    2.0
d    3.0
dtype: float64

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

obj['b':'c'] = 5
print(obj)
a    0.0
b    5.0
c    5.0
d    3.0
dtype: float64

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

data = pd.DataFrame(np.arange(16).reshape(4, 4), index=['Ohio', 'Colorado', 'Utah', 'NewYork'], columns=['one', 'two', 'three', 'four'])
print(data)
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11
NewYork    12   13     14    15

使用索引举例:

print(data['two'])
Ohio         1
Colorado     5
Utah         9
NewYork     13
Name: two, dtype: int32
print(data[['three', 'one']])
          three  one
Ohio          2    0
Colorado      6    4
Utah         10    8
NewYork      14   12

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

print(data[:2])
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7 
print(data[data['three'] > 5])
          one  two  three  four
Colorado    4    5      6     7
Utah        8    9     10    11
NewYork    12   13     14    15

选取行的语法data[:2]十分方便。向[ ]传递单一的元素或列表,就可选择列。另一种用法是通过布尔型DataFrame(比如下面这个由标量比较运算得出的)进行索引:

print(data < 5)
data[data < 5] = 0
print(data)
            one    two  three   four
Ohio       True   True   True   True
Colorado   True  False  False  False
Utah      False  False  False  False
NewYork   False  False  False  False
          one  two  three  four
Ohio        0    0      0     0
Colorado    0    5      6     7
Utah        8    9     10    11
NewYork    12   13     14    15

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

4、用loc和iloc进行选取

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

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

print(data.loc['Colorado', ['two', 'three']])
two      5
three    6
Name: Colorado, dtype: int32

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

print(data.iloc[2, [3, 0, 1]])
four    11
one      8
two      9
Name: Utah, dtype: int32
print(data.iloc[2])
one       8
two       9
three    10
four     11
Name: Utah, dtype: int32 
print(data.iloc[[1, 2], [3, 0, 1]])
          four  one  two
Colorado     7    4    5
Utah        11    8    9

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

print(data.loc[:'Utah', 'two'])
Ohio        1
Colorado    5
Utah        9
Name: two, dtype: int32
print(data.iloc[:, :3][data.three > 5])
          one  two  three
Colorado    4    5      6
Utah        8    9     10
NewYork    12   13     14

所以,在pandas中,有多个方法可以选取和重新组合数据。

 5、整数索引

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

ser = pd.Series(np.arange(3.))
print(ser[-1])
    return self._engine.get_value(s, k, tz=getattr(series.dtype, "tz", None))
  File "pandas\_libs\index.pyx", line 80, in pandas._libs.index.IndexEngine.get_value
  File "pandas\_libs\index.pyx", line 88, in pandas._libs.index.IndexEngine.get_value
  File "pandas\_libs\index.pyx", line 131, in pandas._libs.index.IndexEngine.get_loc
  File "pandas\_libs\hashtable_class_helper.pxi", line 992, in pandas._libs.hashtable.Int64HashTable.get_item
  File "pandas\_libs\hashtable_class_helper.pxi", line 998, in pandas._libs.hashtable.Int64HashTable.get_item
KeyError: -1

以上执行会报错。已经自动产生了0开头的索引,引用-1时则报错!

但是对于非整数索引,则不会产生歧义:

ser2 = pd.Series(np.arange(3.), index=['a', 'b', 'c'])
print(ser2[-1])
2.0

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

ser = pd.Series(np.arange(3.))
print(ser[:1])
0    0.0
dtype: float64 
print(ser.loc[:1])
print(ser.iloc[:1])
0    0.0
1    1.0
dtype: float64
0    0.0
dtype: float64

6、算术运算和数据对齐

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

s1 = pd.Series([7.3, -2.5, 3.4, 1.5], index=['a', 'c', 'd', 'e'])
s2 = pd.Series([-2.1, 3.6, -1.5, 4, 3.1], index=['a', 'c', 'e', 'f', 'g'])
print(s1)
print(s2)
a    7.3
c   -2.5
d    3.4
e    1.5
dtype: float64
a   -2.1
c    3.6
e   -1.5
f    4.0
g    3.1
dtype: float64

将它们相加就会产生:

print(s1+s2)
a    5.2
c    1.1
d    NaN
e    0.0
f    NaN
g    NaN
dtype: float64

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

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

df1 = pd.DataFrame(np.arange(9.).reshape(3, 3), columns=list('bcd'), index=['Ohio', 'Texas', 'Colorado'])
df2 = pd.DataFrame(np.arange(12.).reshape(4, 3), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon'])
print(df1)
print(df2)
            b    c    d
Ohio      0.0  1.0  2.0
Texas     3.0  4.0  5.0
Colorado  6.0  7.0  8.0
          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0

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

print(df1+df2)
            b   c     d   e
Colorado  NaN NaN   NaN NaN
Ohio      3.0 NaN   6.0 NaN
Oregon    NaN NaN   NaN NaN
Texas     9.0 NaN  12.0 NaN
Utah      NaN NaN   NaN NaN

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

7、在算术方法中填充值

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

df1 = pd.DataFrame(np.arange(12).reshape((3, 4)), columns=list('abcd'))
df2 = pd.DataFrame(np.arange(20).reshape((4, 5)), columns=list('abcde'))
df2.loc[1, 'b'] = np.nan
print(df1)
print(df2)
   a  b   c   d
0  0  1   2   3
1  4  5   6   7
2  8  9  10  11
    a     b   c   d   e
0   0   1.0   2   3   4
1   5   NaN   7   8   9
2  10  11.0  12  13  14
3  15  16.0  17  18  19

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

print(df1+df2)
      a     b     c     d   e
0   0.0   2.0   4.0   6.0 NaN
1   9.0   NaN  13.0  15.0 NaN
2  18.0  20.0  22.0  24.0 NaN
3   NaN   NaN   NaN   NaN NaN

使用df1的add方法,传入df2以及一个fill_value参数:

print(df1.add(df2, fill_value=0))
      a     b     c     d     e
0   0.0   2.0   4.0   6.0   4.0
1   9.0   5.0  13.0  15.0   9.0
2  18.0  20.0  22.0  24.0  14.0
3  15.0  16.0  17.0  18.0  19.0

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

print(1/df1)
       a         b         c         d
0    inf  1.000000  0.500000  0.333333
1  0.250  0.200000  0.166667  0.142857
2  0.125  0.111111  0.100000  0.090909
print(df1.rdiv(1))
       a         b         c         d
0    inf  1.000000  0.500000  0.333333
1  0.250  0.200000  0.166667  0.142857
2  0.125  0.111111  0.100000  0.090909方法
方法 说明
add,radd 用于加法(+)的方法
sub,rsub 用于减法(—)的方法
div,rdiv 用于除法(/)的方法
floordiv,rfloordiv 用于底除(//)的方法
mul,rmul 用于乘法(*)的方法
pow,rpow 用于指数(**)的方法

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

print(df1.reindex(columns=df2.columns, fill_value=0))
   a  b   c   d  e
0  0  1   2   3  0
1  4  5   6   7  0
2  8  9  10  11  0

8、DataFrame和Series之间的运算

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

arr = np.arange(12.).reshape((3, 4))
print(arr)
[[ 0.  1.  2.  3.]
 [ 4.  5.  6.  7.]
 [ 8.  9. 10. 11.]]
print(arr-arr[0])
[[0. 0. 0. 0.]
 [4. 4. 4. 4.]
 [8. 8. 8. 8.]]

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

frame = pd.DataFrame(np.arange(12.).reshape(4, 3), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon'])
series = frame.iloc[0]
print(frame)
print(series)
          b     d     e
Utah    0.0   1.0   2.0
Ohio    3.0   4.0   5.0
Texas   6.0   7.0   8.0
Oregon  9.0  10.0  11.0
b    0.0
d    1.0
e    2.0
Name: Utah, dtype: float64

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

print(frame-series)
          b    d    e
Utah    0.0  0.0  0.0
Ohio    3.0  3.0  3.0
Texas   6.0  6.0  6.0
Oregon  9.0  9.0  9.0

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

series2 = pd.Series(range(3), index=['b', 'e', 'f'])
print(frame+series2)
          b   d     e   f
Utah    0.0 NaN   3.0 NaN
Ohio    3.0 NaN   6.0 NaN
Texas   6.0 NaN   9.0 NaN
Oregon  9.0 NaN  12.0 NaN

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

series3 = frame['d']
print(series3)
print(frame.sub(series3, axis='index'))
Utah       1.0
Ohio       4.0
Texas      7.0
Oregon    10.0
Name: d, dtype: float64
          b    d    e
Utah   -1.0  0.0  1.0
Ohio   -1.0  0.0  1.0
Texas  -1.0  0.0  1.0
Oregon -1.0  0.0  1.0

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

9、函数应用和映射

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

frame = pd.DataFrame(np.random.rand(4, 3), columns=list('bde'), index=['Utah', 'Ohio', 'Texas', 'Oregon'])
print(frame)
print(np.abs(frame))
               b         d         e
Utah    0.064023  0.692445  0.397648
Ohio    0.307545  0.700493  0.054521
Texas   0.790282  0.857526  0.176732
Oregon  0.845433  0.030763  0.197624
               b         d         e
Utah    0.064023  0.692445  0.397648
Ohio    0.307545  0.700493  0.054521
Texas   0.790282  0.857526  0.176732
Oregon  0.845433  0.030763  0.197624

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

f = lambda x: x.max() - x.min()
print(frame.apply(f))
b    0.628426
d    0.228863
e    0.327750
dtype: float64

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

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

print(frame.apply(f, axis=1))
Utah      0.632225
Ohio      0.165264
Texas     0.829253
Oregon    0.035064
dtype: float64

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

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

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


print(frame.apply(f))
            b         d         e
min  0.126930  0.002095  0.107104
max  0.847219  0.816830  0.932785

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

print(frame.applymap(format))
                          b                    d                     e
Utah     0.6798017353255632    0.597186588130127   0.19187846531150032
Ohio     0.5847604949858233   0.5343443064790626    0.7629954378984624
Texas   0.16706070519049687  0.29479050054459943    0.6530392097716237
Oregon   0.8484367833746287   0.5218312157057384  0.021621564616659317

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

print(frame['e'].map(format))
Utah        0.4383346697291973
Ohio        0.7597781582580012
Texas     0.011837912674436457
Oregon      0.4294773649163831
Name: e, dtype: object

说明:因为是随机函数,因此产生的值有可能不一样,如要一致,请定义随机种子数。

10、排序和排名

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

obj = pd.Series(range(4), index=['d', 'a', 'b', 'c'])
print(obj.sort_index())
a    1
b    2
c    3
d    0
dtype: int64

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

frame = pd.DataFrame(np.arange(8.).reshape((2, 4)), index=['three', 'one'], columns=['d', 'a', 'b', 'c'])
print(frame.sort_index())
         d    a    b    c
one    4.0  5.0  6.0  7.0
three  0.0  1.0  2.0  3.0
print(frame.sort_index(axis=1))
         a    b    c    d
three  1.0  2.0  3.0  0.0
one    5.0  6.0  7.0  4.0

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

print(frame.sort_index(axis=1, ascending=False))
         d    c    b    a
three  0.0  3.0  2.0  1.0
one    4.0  7.0  6.0  5.0

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

obj = pd.Series([4, 7, -3, 2])
print(obj.sort_values())
2   -3
3    2
0    4
1    7
dtype: int64

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

obj = pd.Series([4, np.nan, 7, np.nan, -3, 2])
print(obj.sort_values())
4   -3.0
5    2.0
0    4.0
2    7.0
1    NaN
3    NaN
dtype: float64

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

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

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

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

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

obj = pd.Series([7, -5, 7, 4, 2, 0, 4])
print(obj.rank())
0    6.5
1    1.0
2    6.5
3    4.5
4    3.0
5    2.0
6    4.5
dtype: float64

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

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

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

也可以按降序进行排名:

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

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

frame = pd.DataFrame({'b': [4.3, 7, -3, 2], 'a': [0, 1, 0, 1], 'c': [-2, 5, 8, -2.5]})
print(frame)
print(frame.rank(axis='columns'))
     b  a    c
0  4.3  0 -2.0
1  7.0  1  5.0
2 -3.0  0  8.0
3  2.0  1 -2.5
     b    a    c
0  3.0  2.0  1.0
1  3.0  1.0  2.0
2  1.0  2.0  3.0
3  3.0  2.0  1.0

  

方法 说明
average 默认:在相等分组中,为各个值分配平均排名
min 使用整个分组的最小排名
max 使用整个分组的最大排名
first 按值在原始数据中的出现顺序分配排名
dense 类似于min方法,但是排名总是在组间增加1,而不是组中相同的元素数

11、带有重复标签的轴索引

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

obj = pd.Series(range(5), index=['a', 'a', 'b', 'b', 'c'])
print(obj)
a    0
a    1
b    2
b    3
c    4
dtype: int64

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

print(obj.index.is_unique)
False

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

print(obj['a'])
a    0
a    1
dtype: int64 
print(obj['c'])
4

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

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

df = pd.DataFrame(np.random.randn(4, 3), index=['a', 'a', 'b', 'b'])
print(df)
print(df.loc['b'])
          0         1         2
a  1.256824  1.291061 -0.685927
a -0.407044 -0.353979 -1.864022
b  0.037937  0.517678  0.680367
b  3.037344 -0.065109  0.588987
          0         1         2
b  0.037937  0.517678  0.680367
b  3.037344 -0.065109  0.588987

二、汇总和计算描述统计

12、汇总和计算描述统计

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

df = pd.DataFrame([[1.4, np.nan], [7.1, -4.5], [np.nan, np.nan], [0.75, -1.3]], index=['a', 'b', 'c', 'd'], columns=['one', 'two'])
print(df)
    one  two
a  1.40  NaN
b  7.10 -4.5
c   NaN  NaN
d  0.75 -1.3

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

print(df.sum())
one    9.25
two   -5.80
dtype: float64 

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

print(df.sum(axis=1))
a    1.40
b    2.60
c    0.00
d   -0.55
dtype: float64

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

print(df.mean(axis=1, skipna=False))
a      NaN
b    1.300
c      NaN
d   -0.275
dtype: float64

如下列出了这些约简方法的常用选项:

选项 说明
axis 约简的轴。由DataFrame的行用0,列用1
skipna 排除缺失值,默认值为True
level 如果轴是层次化索引的(即MultiIndex),则根据level分组约简

 

有些方法(如idxmin和idxmax)返回的是间接统计(比如达到最小值或最大值的索引):

print(df.idxmax())
one    b
two    d
dtype: object

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

print(df.cumsum())
    one  two
a  1.40  NaN
b  8.50 -4.5
c   NaN  NaN
d  9.25 -5.8

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

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

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

obj = pd.Series(['a', 'a', 'b', 'c']*4)
print(obj.describe())
count     16
unique     3
top        a
freq       8
dtype: object

如下列表列出了所有与描述统计相关的方法:

 表:描述和汇总统计

方法 说明
count 非NA值的数量
describe 针对Series或各DataFrame列计算汇总统计
min、max 计算最小值和最大值
argmin、argmax 计算能够获取到最小值和最大值的索引位置(整数)
idxmin、idxmax 计算能够获取到最小值和最大值的索引值
quantile 计算样本的分位数(0到1)
sum 值的总和
mean 值的平均数
median 值的算术中位数(50%分位数)
mad 根据平均值计算平均绝对离差
var 样本值的方差
std 样本值的标准差
skew 样本值的偏度(三阶矩)
kurt 样本值的峰度(四阶矩)
cumsum 样本值的累计和
cummin、cummax 样本值的累计最大值和累计最小值
cumprod 样本值的累计积
diff 计算一阶差分(对时间序列很有用)
pct_change 计算百分数变化

 

13、相关系数与协方差

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

import pandas_datareader.data as web

all_data = {ticker: web.get_data_yahoo(ticker) for ticker in ['AAPL', 'IBM', 'MSFT', 'GOOG']}
price = pd.DataFrame({ticker: data['Adj Close'] for ticker, data in all_data.items()})
volume = pd.DataFrame({ticker: data['Volume'] for ticker, data in all_data.items()})
returns = price.pct_change()
print(returns.tail())
                AAPL       IBM      MSFT      GOOG
Date                                              
2019-11-22 -0.000878  0.003736  0.000736 -0.004618
2019-11-25  0.017534  0.012133  0.010963  0.008762
2019-11-26 -0.007809 -0.006472  0.005290  0.005250
2019-11-27  0.013432 -0.009771  0.001908 -0.000426
2019-11-29 -0.002203  0.005083 -0.006171 -0.006116

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

print(returns['MSFT'].corr(returns['IBM']))
0.48748863221002536
print(returns['MSFT'].cov(returns['IBM']))
9.403469349907563e-05

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

print(returns.MSFT.corr(returns.IBM))
0.48748863221002536

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

相关系数:

print(returns.corr())
          AAPL       IBM      MSFT      GOOG
AAPL  1.000000  0.406404  0.575217  0.523380
IBM   0.406404  1.000000  0.487489  0.414135
MSFT  0.575217  0.487489  1.000000  0.660486
GOOG  0.523380  0.414135  0.660486  1.000000

协方差矩阵:

print(returns.cov())
          AAPL       IBM      MSFT      GOOG
AAPL  0.000246  0.000083  0.000133  0.000125
IBM   0.000083  0.000170  0.000094  0.000082
MSFT  0.000133  0.000094  0.000218  0.000148
GOOG  0.000125  0.000082  0.000148  0.000231

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

print(returns.corrwith(returns.IBM))
AAPL    0.406404
IBM     1.000000
MSFT    0.487489
GOOG    0.414135
dtype: float64

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

print(returns.corrwith(volume))
AAPL   -0.119077
IBM    -0.133046
MSFT   -0.085162
GOOG   -0.005952
dtype: float64

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

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

  还有一类方法可以从一维Series的值中抽取信息。

obj = pd.Series(['c', 'a', 'd', 'a', 'a', 'b', 'b', 'c', 'c'])
uniques = obj.unique()
print(uniques)
['c' 'a' 'd' 'b']

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

print(obj.value_counts())
a    3
c    3
b    2
d    1
dtype: int64

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

print(pd.value_counts(obj.values, sort=False))
d    1
c    3
b    2
a    3
dtype: int64

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

mask = obj.isin(['b', 'c'])
print(mask)
0     True
1    False
2    False
3    False
4    False
5     True
6     True
7     True
8     True
dtype: bool

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

print(pd.Index(unique_vals).get_indexer(to_match))
[0 2 1 1 0 2]

如下表给出了这几个方法的一些参考信息:

方法 说明
isin 计算一个表示'Series各值是否包含于传入的值序列中'的布尔型数组
match 计算一个数组中的各值到另一个不同值数组的整数索引;对于数据对齐和连接类型的操作十分有用
unique 计算Series中的唯一值数组,按发现的顺序返回
value_counts 返回一个Series,其索引为唯一值,其值为频率,按计数值降序排列

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

data = pd.DataFrame({'Qul': [1, 3, 4, 3, 4], 'Qu2': [2, 3, 1, 2, 3], 'Qu3': [1, 5, 2, 4, 4]})
print(data)
   Qul  Qu2  Qu3
0    1    2    1
1    3    3    5
2    4    1    2
3    3    2    4
4    4    3    4

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

result = data.apply(pd.value_counts).fillna(0)
print(result)
   Qul  Qu2  Qu3
1  1.0  1.0  1.0
2  0.0  2.0  1.0
3  2.0  2.0  0.0
4  2.0  0.0  2.0
5  0.0  0.0  1.0

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

推荐阅读