数据分析基础 (下)

Data Analytics Introduction - Part 2

范叶亮 Leo Van

目录

  • pandas 简介
  • Python 数据载入和存储
  • Python 数据规整

pandas 简介

pandas 简介

pandas 所包含的数据结构和数据处理工具的设计使得在 Python 中进行数据清洗和分析非常便捷。尽管 pandas 采用了很多 NumPy 的代码风格,但最大的不同在于 pandas 是用来处理表格型或异质类型数据的,而 NumPy 则相反,它更适合处理同质类型的数据。

在后续内容中,我们会使用如下的快捷方式导入 pandas:

import pandas as pd
from pandas import Series, DataFrame

pandas 中的两个常用的工具数据结构:Series 和 DataFrame 可以为大多数应用提供一个有效易用的基础。

Series

Series 是一种一维的数据型对象,它包含了一个值序列(与 NumPy 中的类型相似),并且包含了数据标签,称之为索引(index),最简单的序列可以仅由一个数组构成:

s = pd.Series([1, 2, 3])
s
0    1
1    2
2    3
dtype: int64

交互式环境中 Series 的表示左边为索引,右边为值。由于我们不为数据指定索引,默认生成的索引是从 0 到 N-1(N 是数据的长度)。可以通过 values 属性和 index 属性分别获取 Series 对象的值和索引。

通过添加索引为每个数据点添加标识:

s = pd.Series([1, 2, 3], index=['a', 'b', 'c'])
s
a    1
b    2
c    3
dtype: int64
s.index
Index(['a', 'b', 'c'], dtype='object')
s.values
array([1, 2, 3])

Series

与 NumPy 相比,可以在数据中选取数据时使用标签:

s['a']
1
s['b'] = 6
s[['c', 'b', 'a']]
c    3
b    6
a    1
dtype: int64

使用 NumPy 函数或 NumPy 风格操作:

s[s > 1]
b    6
c    3
dtype: int64
s * 2
a     2
b    12
c     6
dtype: int64

Series

Series 可以被看做是一个长度固定且有序的字典,因为它将索引值和数据值按位置配对。

'b' in s
True
'e' in s
False

可以使用字典生成一个 Series

d = {
    'Ohio': 35000, 'Texas': 71000,
    'Oregon': 16000, 'Utah': 5000
}
s1 = pd.Series(d)
s1
Ohio      35000
Texas     71000
Oregon    16000
Utah       5000
dtype: int64

当把字典传递给 Series 时,产生的 Series 的索引将是排序好的字典键。也可以将字典键按照所需的顺序传递给构造函数参数 index,从而生成的 Series 的索引顺序将与传入的字典键相符。

states = ['California', 'Ohio', 'Oregon', 'Texas']
s2 = pd.Series(d, index=states)

Series

因为 California 没有出现在 d 的键中,它对应的值是 NaN(Not a Number),是 pandas 中用于标记缺失值的方式。因为 Utah 不在 d 中,它被排除在结果外。

pandas 使用 isnullnotnull 函数检查缺失值:

pd.isnull(s2)
California     True
Ohio          False
Oregon        False
Texas         False
dtype: bool
pd.notnull(s2)
California    False
Ohio           True
Oregon         True
Texas          True
dtype: bool

isnullnotnull 也是 Series 实例方法。

自动对齐索引是 Series 的一个重要特性:

s1 + s2
California         NaN
Ohio           70000.0
Oregon         32000.0
Texas         142000.0
Utah               NaN
dtype: float64

该特性与数据库中的 join 操作非常类似。

DataFrame

DataFrame 表示的是矩阵的数据表,它包含已排序的列集合,每一列可以是不同的值类型(数值、字符串、布尔值等)。DataFrame 既有行索引也有列索引,它可以被视为一个共享相同索引的 Series 的字典。在 DataFrame 中,数据被存储为一个以上的二维块,而不是列表、字典或其他一维数组的结合。

有多种方式可以构建 DataFrame,其中最常用的方式是利用包含等长度列表或 NumPy 数组的字典来构建 DataFrame:

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

DataFrame

对于大型 DataFrame,head 方法会选出头部的 n 行:

df.head(2)
  state  year  pop
0  Ohio  2000  1.5
1  Ohio  2001  1.7

DataFrame 可以按照指定列的顺序排序:

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

DataFrame 中的一列可以按照字典型标记或属性检索为 Series:

df['state']
0      Ohio
1      Ohio
2      Ohio
3    Nevada
4    Nevada
5    Nevada
Name: state, dtype: object
df.year
0    2000
1    2001
2    2002
3    2000
4    2001
5    2002
Name: year, dtype: int64

请注意,返回的 Series 与原 DataFrame 有相同的索引,且 Series 的 names 属性也会被合理的设置。

DataFrame

当将列表或数组赋值给一个列时,值的长度必须和 DataFrame 匹配。如果将 Series 赋值给一列时,Series 的索引将会按照 DataFrame 的索引重新排序,并在空缺的地方填充缺失值:

val = pd.Series([-1.2, -1.5, -1.7], index=[1, 3, 4])
df['debt'] = val
df
    state  year  pop  debt
0    Ohio  2000  1.5   NaN
1    Ohio  2001  1.7  -1.2
2    Ohio  2002  3.6   NaN
3  Nevada  2000  2.4  -1.5
4  Nevada  2001  2.9  -1.7
5  Nevada  2002  3.2   NaN

del 方法可以用于移除之前新建的列:

del df['debt']
df
    state  year  pop
0    Ohio  2000  1.5
1    Ohio  2001  1.7
2    Ohio  2002  3.6
3  Nevada  2000  2.4
4  Nevada  2001  2.9
5  Nevada  2002  3.2

从 DataFrame 中选取的列是数据的视图,不是拷贝。因此,对 Series 的修改会映射到 DataFrame 中。如果需要复制,则应当显示地使用 Series 的 copy 方法。

DataFrame

可以用嵌套字典生成 DataFrame:

d = {
    'Neveda': {2001: 2.4, 2002: 2.9},
    'Ohio': {2000: 1.5, 2001: 1.7, 2002: 3.6}
}
df = pd.DataFrame(d)
df
      Neveda  Ohio
2001     2.4   1.7
2002     2.9   3.6
2000     NaN   1.5

可以使用类似 NumPy 的方法对 DataFrame 进行转置:

df.T
        2001  2002  2000
Neveda   2.4   2.9   NaN
Ohio     1.7   3.6   1.5

如果 DataFrame 索引和列有 name 属性,也会被显示:

df.index.name = 'year'
df.columns.name = 'state'
df
state  Neveda  Ohio
year               
2001      2.4   1.7
2002      2.9   3.6
2000      NaN   1.5

DataFrame 的 values 属性会返回二维的 ndarray

df.values
array([[2.4, 1.7],
       [2.9, 3.6],
       [nan, 1.5]])

索引对象

pandas 中的索引对象是用于储存轴标签和其他元数据的(例如:轴名称或标签),在构造 Series 或 DataFrame 时,所使用的任意数组或标签序列都可以在内部转换为索引对象。

s = pd.Series(range(3), index=['a', 'b', 'c'])
index = s.index
index
Index(['a', 'b', 'c'], dtype='object')
index[1:]
Index(['b', 'c'], dtype='object')

索引对象是不可变的,因此用户是无法修改索引对象的,这使得在多种数据结构中分享索引对象是安全的。

与 Python 集合不同,pandas 索引对象可以包含重复标签:

dup_idx = pd.Index(['a', 'a', 'b', 'b'])
dup_idx
Index(['a', 'a', 'b', 'b'], dtype='object')

根据重复标签筛选,会选取所有重复标签对应的数据。

索引对象

方法 描述 方法 描述
append 将额外的索引对象粘贴到原索引后,
产生一个新的索引
difference 计算两个索引的差集
intersection 计算两个索引的交集 union 计算两个索引的并集
isin 计算表示每一个值是否在传值容器
中的布尔数组
delete 将位置 i 的元素删除,
并产生新的索引
drop 根据传参删除指定索引值,
并产生新的索引
insert 在位置 i 插入元素,
并产生新的索引
is_monotonic 如果索引序列递增则返回 True is_unique 如果索引序列唯一则返回 True
unique 计算索引的唯一值序列

重建索引

reindex 用于创建一个符合新索引的新对象:

s = pd.Series([2, 4, 1, 3], index=['d', 'b', 'a', 'c'])
s
d    2
b    4
a    1
c    3
dtype: int64

在 DataFrame 中,reindex 可以改变行索引,列索引,也可以同时改变二者。当仅传入一个序列时,结果中的行会重建索引,列可以使用 columns 关键字重建索引。

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

重建索引

reindex 方法的参数详见下表:

参数 描述
index 新建作为索引的序列,可以是索引示例或任意其他序列型 Python 数据结构,索引使用时无需复制
method 插值方法,ffill 为向前填充,bfill 为向后填充
fill_value 通过重新索引引入缺失数据时使用的替代值
limit 当前向或后向填充时,所需填充的最大尺寸间隔
tolerance 当前向或后向填充时,所需填充的不精确匹配下的最大尺寸间隔
level 匹配 MultiIndex 级别的简单索引
copy 如果为 True,即使新索引等于旧索引,也总是复制底层数据

删除条目

drop 方法返回含有指示值或轴向上删除值的新对象:

s = pd.Series(np.arange(3.), index=['a', 'b', 'c'])
s
a    0.0
b    1.0
c    2.0
dtype: float64
new_s = s.drop('b')
new_s
a    0.0
c    2.0
dtype: float64

在 DataFrame 中,索引值可以从轴向上删除:

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

删除条目

drop 会根据行标签删除值(轴 0):

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

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

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

通过 inplace 可以直接修改原对象而不返回新对象:

df.drop('Utah', inplace=True)
df
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
New York   12   13     14    15

索引,选择和过滤

Series 的索引与 NumPy 数组索引类似:

s = pd.Series(
    np.arange(4.), index=['a', 'b', 'c', 'd']
)
s
a    0.0
b    1.0
c    2.0
d    3.0
dtype: float64
s['b']
1.0
s[1]
1.0
s[2:4]
c    2.0
d    3.0
dtype: float64

索引,选择和过滤

s[['b', 'a']]
b    1.0
a    0.0
dtype: float64
s[s < 2]
a    0.0
b    1.0
dtype: float64

普通的 Python 切片中是不包含尾部的,Series 的切片与之不同:

s['b':'c']
b    1.0
c    2.0
dtype: float64

使用这些方法设置值是会修改 Series 相应的部分:

s['b':'c'] = 6
s
a    0.0
b    6.0
c    6.0
d    3.0
dtype: float64

索引,选择和过滤

使用单个值或序列,可以从 DataFrame 中索引出一个或多个列:

df = pd.DataFrame(
    np.arange(16).reshape((4, 4)),
    index=['Ohio', 'Colorado', 'Utah', 'New York'],
    columns=['one', 'two', 'three', 'four']
)
df
          one  two  three  four
Ohio        0    1      2     3
Colorado    4    5      6     7
Utah        8    9     10    11
New York   12   13     14    15
df['two']
Ohio         1
Colorado     5
Utah         9
New York    13
Name: two, dtype: int64
df[['three', 'one']]
          three  one
Ohio          2    0
Colorado      6    4
Utah         10    8
New York     14   12

索引,选择和过滤

可以根据一个布尔值数组切片或选择数据:

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

行选择语法 df[:2] 非常方便,传递单个元素或一个列表到 [] 中可以选择列。

通过布尔值也可以对 DataFrame 进行索引,布尔值 DataFrame 可以是对标量值进行比较产生:

df[df < 6] = 0
df
          one  two  three  four
Ohio        0    0      0     0
Colorado    0    0      6     7
Utah        8    9     10    11
New York   12   13     14    15

索引,选择和过滤

使用 lociloc 可以对 DataFrame 在行上的标签索引,轴标签(loc)或整数标签(iloc)以 NumPy 风格的语法从 DataFrame 中选出数组的行和列的子集。

df.loc['Colorado', ['two', 'three']]
two      0
three    6
Name: Colorado, dtype: int64
df.iloc[2, [3, 0, 1]]
four    11
one      8
two      9
Name: Utah, dtype: int64

除了单个标签或标签列表之外,索引功能还可以用于切片:

df.loc[:'Utah', 'two']
Ohio        0
Colorado    0
Utah        9
Name: two, dtype: int64
df.iloc[:, :3][df.three > 5]
          one  two  three
Colorado    0    0      6
Utah        8    9     10
New York   12   13     14

索引选项

类型 描述
df[val] 从 DataFrame 中选择单列或列序列;特殊情况的便利:布尔数组(过滤行),
切片(切片行)或布尔值 DataFrame(根据某些标准设置的值)
df.loc[val] 根据标签选择 DataFrame 的单行或多行
df.loc[:, val] 根据标签选择单列或多列
df.loc[val1, val2] 同时选择行和列的一部分
df.iloc[where] 根据整数位置选择单行或多行
df.iloc[:, where] 根据整数位置选择单列或多列
df.iloc[where_i, where_j] 根据整数位置选择行和列
df.at[label_i, label_j] 根据行、列标签选择单个标量值
df.iat[i, j] 根据行、列整数位置选择单个标量值

算术和数据对齐

不同索引对象之间的算术行为是 pandas 提供的一项重要特性,将对象相加时,如果存在某个索引对不同,则返回结果的索引将是索引对的并集。

s1 = pd.Series(
    [1, 2, 3, 4], index=['a', 'c', 'd', 'e'])
s2 = pd.Series(
    [5, 6, 7, 8, 9], index=['a', 'c', 'e', 'f', 'g'])
s1
a    1
c    2
d    3
e    4
dtype: int64
s2
a    5
c    6
e    7
f    8
g    9
dtype: int64

将这些对象相加则会产生:

s1 + s2
a     6.0
c     8.0
d     NaN
e    11.0
f     NaN
g     NaN
dtype: float64

没有交叠的标签位置上,内部数据对齐会产生缺失值。缺失值会在后续的算数操作上产生影响。

算术和数据对齐

在 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'])
df1
          b  c  d
Ohio      0  1  2
Texas     3  4  5
Colorado  6  7  8
df2
        b   d   e
Utah    0   1   2
Ohio    3   4   5
Texas   6   7   8
Oregon  9  10  11

将这些对象加在一起,返回一个 DataFrame,它的索引和列是每个 DataFrame 的索引和列的并集。

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

由于 ce 列并不是两个 DataFrame 共有的列,这两列中产生了缺失值。对于行标签不同的 DataFrame 对象也是如此。

算术和数据对齐

df1 上使用 add 方法,我们将 df2 和一个 fill_value 作为参数传入:

df1.add(df2, fill_value=0)
            b    c     d     e
Colorado  6.0  7.0   8.0   NaN
Ohio      3.0  1.0   6.0   5.0
Oregon    9.0  NaN  10.0  11.0
Texas     9.0  4.0  12.0   8.0
Utah      0.0  NaN   1.0   2.0

每个方法都有一个以 r 开头的副本,副本方法的参数是翻转的,例如:1 / df1df1.rdiv(1) 是等价的。

方法 描述
add, radd 加法(+)
sub, rsub 减法(-)
div, rdiv 除法(/)
floordiv, rfloordiv 整除(//)
mul, rmul 乘法(*)
pow, rpow 幂次方(**)

算术和数据对齐

DataFrame 和 Series 间的算数操作与 NumPy 中不同维度数组间的操作类似。

df = pd.DataFrame(
    np.arange(12).reshape(4, 3), columns=list('bde'),
    index=['Utah', 'Ohio', 'Texas', 'Oregon'])
s = df.iloc[0]
df
        b   d   e
Utah    0   1   2
Ohio    3   4   5
Texas   6   7   8
Oregon  9  10  11
s
b    0
d    1
e    2
Name: Utah, dtype: int64

默认情况下,DataFrame 和 Series 数学操作中会将 Series 索引和 DataFrame 的列进行匹配,并广播到各行:

df - s
        b  d  e
Utah    0  0  0
Ohio    3  3  3
Texas   6  6  6
Oregon  9  9  9

如果索引值不在 DataFrame 的列中,也不再 Series 的索引中,则对象会重建索引并形成联合。如果想改为在列上进行广播,在行上匹配,则必须使用算法方法中的一种,通过 axis 参数进行轴匹配,axis='index'axis=0 为行匹配。

函数应用和映射

NumPy 的通用函数(逐元素数组方法)对 pandas 对象也有效:

df = pd.DataFrame(
    np.random.randn(4, 3), columns=list('bde'),
    index=['Utah', 'Ohio', 'Texas', 'Oregon'])
df
               b         d         e
Utah   -0.752446 -0.709925 -0.337916
Ohio    0.726541  2.137040 -2.288782
Texas   0.674664 -0.896596  2.181261
Oregon  0.885203 -1.067982 -1.518018
np.abs(df)
               b         d         e
Utah    0.752446  0.709925  0.337916
Ohio    0.726541  2.137040  2.288782
Texas   0.674664  0.896596  2.181261
Oregon  0.885203  1.067982  1.518018

一个常用的操作是将函数应用到一行或一列的一维数组上,DataFrame 的 apply 方法可以实现该功能:

f = lambda x: x.max() - x.min()
df.apply(f)
b    1.637649
d    3.205021
e    4.470043
dtype: float64

函数应用和映射

通过传递 axis='columns'apply 函数,函数会被每行调用一次:

df.apply(f, axis='columns')
Utah      0.414531
Ohio      4.425821
Texas     3.077857
Oregon    2.403221
dtype: float64

逐元素的 Python 函数也可以,例如对 df 中的每个值计算一个格式化的字符串,可以使用 applymap 方法:

f = lambda x: '{:2f}'.format(x)
df.applymap(f)
                b          d          e
Utah    -0.752446  -0.709925  -0.337916
Ohio     0.726541   2.137040  -2.288782
Texas    0.674664  -0.896596   2.181261
Oregon   0.885203  -1.067982  -1.518018

使用 applymap 作为函数名是因为 Series 有 map 方法,可以将一个逐元素的函数应用到 Series 上:

df['e'].map(f)
Utah      -0.337916
Ohio      -2.288782
Texas      2.181261
Oregon    -1.518018
Name: e, dtype: object

排序和排名

如需按照行或列索引进行字典型排序,需要使用 sort_index 方法,其返回一个新的、排序好的对象:

s = pd.Series(range(4), index=list('dabc'))
s.sort_index()
a    1
b    2
c    3
d    0
dtype: int64

在 DataFrame 中,可以在各个轴上按索引排序:

df = pd.DataFrame(
    np.arange(8).reshape((2, 4)), index=['two', 'one'],
    columns=['d', 'a', 'b', 'c'])
df.sort_index()
     d  a  b  c
one  4  5  6  7
two  0  1  2  3
df.sort_index(axis=1)
     a  b  c  d
two  1  2  3  0
one  5  6  7  4

数据默认会使用升序排序,也可以使用降序:

df.sort_index(axis=1, ascending=False)
     d  c  b  a
two  0  3  2  1
one  4  7  6  5

排序和排名

如果根据 Series 的值进行排序,可使用 sort_values

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

默认情况下,所有缺失值会排序到尾部:

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

排序和排名

对 DataFrame 排序时,可以使用一列或多列作为排序键,通过设置 sort_values 的参数 by 实现:

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

排名函数 rank 可以对数据点从 1 分配名次:

s = pd.Series([2, 1, 4, 2])
s.rank()
0    2.5
1    1.0
2    4.0
3    2.5
dtype: float64
s.rank(method='first')
0    2.0
1    1.0
2    4.0
3    3.0
dtype: float64

排序和排名

DataFrame 可以对行或列计算排名:

df = pd.DataFrame({
    'b': [4.3, 7, -3, 2],
    'a': [0, 1, 0, 1],
    'c':[-2, 5, 8, -2.5]
})
df
     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
df.rank(axis='columns')
     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 而非组元素数量

描述性统计

pandas 装配了一个常用数学、统计学方法的集合,与 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']
)
df
    one  two
a  1.40  NaN
b  7.10 -4.5
c   NaN  NaN
d  0.75 -1.3

调用 sum 方法返回一个包含列上加和的 Series:

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

传入 axis='columns'axis=1 则沿着行求和:

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

通过 skipna=False 可以不排除 NA 值。

描述性统计

idmaxidmin 返回的是间接统计信息,除了归约方法外,有的方法是积累型方法:

df.idxmax()
one    b
two    d
dtype: object
df.cumsum()
    one  two
a  1.40  NaN
b  8.50 -4.5
c   NaN  NaN
d  9.25 -5.8

还有一类方法不是归约型方法也不是积累型方法,describe 即其中之一,它一次性产生多个汇总统计:

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

对于非数值类型数据,则产生另一种汇总统计:

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

描述性统计

方法 描述 方法 描述
count 非 NA 值个数 describe 各列汇总值
min, max 计算最小/大值 argmin, argmax 计算最小/大值的索引位置
idxmin, idmax 计算最小/大值的索引标签 quantile 计算样本的分位数
sum 加和 mean 均值
median 中位数 mad 平均值的平均绝对偏差
prod 连乘 var, std 样本方差/标准差
skew, kurt 偏度/峰度 cumsum 累加
cummin, cummax 累积值的最小/大值 cumprod 累积
diff 计算第一个算术差值 pct_change 计算百分比

相关性和协方差

import yfinance as yf
d = {ticker: yf.download(ticker, auto_adjust=False)
    for ticker in ['AAPL', 'IBM', 'MSFT']}
p = pd.DataFrame({ticker: data['Adj Close'][ticker]
    for ticker, data in d.items()})
v = pd.DataFrame({ticker: data['Volume'][ticker]
    for ticker, data in d.items()})

Series 的 corr 计算的是两个 Series 中的重叠的,非 NA 的,按索引对齐的值的相关性,cov 计算协方差:

p['MSFT'].corr(p['IBM'])
-0.0010077285210880053
p['MSFT'].cov(p['IBM'])
-0.02006614347919805

DataFrame 的 corrcov 方法会分别以 DataFrame 形式返回相关性和协方差矩阵:

p.corr()
          AAPL       IBM      MSFT
AAPL  1.000000  0.232372  0.117309
IBM   0.232372  1.000000 -0.001008
MSFT  0.117309 -0.001008  1.000000
p.cov()
           AAPL        IBM       MSFT
AAPL  18.717995   3.629957   2.798935
IBM    3.629957  13.036918  -0.020066
MSFT   2.798935  -0.020066  30.413456

相关性和协方差

DataFrame 的 corrwith 方法可以计算出 DataFrame 行或列与另一个序列或 DataFrame 的相关性。当传入一个 Series 时,会返回一个含有为每列计算相关性值的 Series:

p.corrwith(p.IBM)
AAPL    0.232372
IBM     1.000000
MSFT   -0.001008
dtype: float64

传入一个 DataFrame 时,会计算匹配到列名的相关性数值:

p.corrwith(v)
AAPL   -0.052422
IBM    -0.428928
MSFT   -0.251407
dtype: float64

传入 axis='colums' 会逐行地进行计算。

唯一值、计数和成员属性

unique 函数可以给出 Series 中的唯一值:

s = pd.Series([
    'c', 'a', 'd', 'a', 'a', 'b', 'b', 'c', 'c'])
s.unique()
array(['c', 'a', 'd', 'b'], dtype=object)

sort 方法可以对其进行排序,value_counts 可以计算 Series 包含的值的个数:

s.value_counts()
c    3
a    3
b    2
d    1
Name: count, dtype: int64

为了方便,返回的 Series 会按照数量降序排序,value_counts 也是有效的 pandas 顶层方法,也可以用于任意数组或序列:

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

唯一值、计数和成员属性

isin 执行向量化的成员属性检查,还可以将数据集以 Series 或 DataFrame 一列的形式过滤为数据集的值子集:

s
0    c
1    a
2    d
3    a
4    a
5    b
6    b
7    c
8    c
dtype: object
s.isin(['b', 'c'])
0     True
1    False
2    False
3    False
4    False
5     True
6     True
7     True
8     True
dtype: bool

isin 相关的 Index.get_indexer 方法,可以提供一个索引数组,这个索引数组可以将可能非唯一值数组转换为另一个唯一值数组:

to_match = pd.Series(['c', 'a', 'b', 'b', 'c', 'a'])
unique_vals = pd.Series(['c', 'b', 'a'])
pd.Index(unique_vals).get_indexer(to_match)
array([0, 2, 1, 1, 0, 2])

数据载入和存储

数据读取

将表格型数据读取为 DataFrame 对象是 pandas 的重要特性,下表为常用的数据读取函数:

函数 描述
read_csv 从文件、URL 或文件型对象读取分隔好的数据,默认分隔符为 ,
read_table 从文件、URL 或文件型对象读取分隔符的数据,默认分隔符为 \t
read_fwf 从特定宽度格式的文件中读取数据(无分隔符)
read_clipboard read_table 的剪切板版本
read_excel 从 Excel 的 XLS 或 XLSX 文件中读取表格数据
read_hdf 读取用 pandas 存储的 HDF5 文件
read_html 从 HTML 文件中读取所有表格数据

数据读取

函数 描述
read_json 从 JSON 字符串中读取数据
read_msgpack 读取 MessagePackage 二进制格式的 pandas 数据
read_pickle 读取以 Python pickle 格式存储的任意对象
read_sas 读取存储在 SAS 系统中定制存储格式的 SAS 数据集
read_sql 将 SQL 查询的结果读取为 pandas 的 DataFrame
read_stata 读取 Stata 格式的数据集
read_feather 读取 Feather 二进制格式

数据读取

read_csv / read_table 的函数参数如下表所示:

参数 描述
path 表明文件系统位置的字符串,URL 或文件型对象
sep, delimiter 用于分割每行字段的字符序列或正则表达式
header 用作列名的行号,默认是 0,如果没有列名则为 None
index_col 用作结果中行索引的列号或列名,可以是一个单一的名称/数字,也可以是一个分层索引
names 结果的列名列表,和 header=None 一起用
skiprows 从文件开头处起,需要跳过的行数或行号列表
comment 在行结尾处分隔注释的字符

数据读取

参数 描述
parse_dates 尝试将数据解析为 datetime,默认是 False,如果为 True,将尝试解析所有的列。也可以指定列号或列名列表来进行解析。如果列表的元素是元组或列表,将会把多个列组合在一起进行解析(例如:日期/时间将拆分为两列)
keep_date_col 如果连接列到解析日期上,保留被连接的列,默认是 False
converters 包含列名称映射到函数的字典(例如:{'foo': f} 会把函数 f 应用到 foo
dayfirst 解析非明确日期时,按照国际格式处理(例如:7/6/2012 -> June 7, 2012),默认为 False
date_parser 用于解析日期的函数

数据读取

参数 描述
nrows 从文件开头处理读入的行数
iterator 返回一个 TextParser 对象,用于零散地读入文件
chunksize 用于迭代的块大小
skip_footer 忽略文件尾部的行数
verbose 打印各种解析器输出的信息,比如位于非数值列中的缺失值数量
encoding Unicode 文本编码(例如:utf-8 用于表示 UTF-8 编码的文本)
squeeze 如果解析数据只包含一列,返回一个 Series
thousands 千位分隔符(例如:,.

数据写入

使用 DataFrame 的 to_csv 方法,可以将数据导出为逗号分隔符文件:

df = pd.DataFrame(
    [['one', 'two', 'three'], [1, 2, 3], [3.0, np.nan, 6.6]],
    columns=['a', 'b', 'c']
)
df.to_csv('tmp.csv')
# ,a,b,c
# 0,one,two,three
# 1,1,2,3
# 2,3.0,,6.6

通过 sep 可是设置分隔符:

import sys
df.to_csv(sys.stdout, sep='|')
|a|b|c
0|one|two|three
1|1|2|3
2|3.0||6.6

na_rep 可以对缺失值进行标注,indexheader 用于控制是否显示行号和列名:

df.to_csv(sys.stdout, na_rep='NULL',
    index=False, header=False)
one,two,three
1,2,3
3.0,NULL,6.6

CSV 分隔格式

参数 描述
delimiter 一般用于分隔字段的字符,默认是 ,
lineterminator 行终止符,默认是 \r\n,读取器会忽略行终止符并识别跨平台行终止符
quotechar 用在含有特殊字符字段中的引号,默认是 "
quoting 引用惯例。选项包括 csv.QUOTE_ALL(引用所有的字段),csv.QUOTE_MINIMAL(只是用特殊字符,如分隔符),csv.QUOTE_NONMUERICcsv.QUOTE_NONE(不引用),默认为 QUOTE_MINIMAL
skipinitialspace 忽略每个分隔符后的空白,默认是 False
doublequote 如何处理字段内部的引号,如果为 True,则是双引号
escapechar 当引用设置为 csv.QUOTE_NONE 时用于转义分隔符的字符串,默认是禁用的

JSON 数据

JSON(JavaScript Object Notation)是一种常用的数据格式,其基本类型包括:对象(字典)、数组(列表)、字符串、数字、布尔值和空值,对象中的所有键必须是字符串。在 Python 中可以利用内建的 json 模块处理 JSON。

js = '''
{
  "name": "Wes",
  "place_lived": ["United States", "Spain", "Germany"],
  "pet": null,
  "siblings": [
    {"name": "Scott", "age": 30, "pets": ["Zeus", "Zuko"]},
    {"name": "Katie", "age": 38, "pets": ["Sixes", "Stache", "Cisco"]}
  ]
}
'''

JSON 数据

利用 json.loads 可以将 JSON 字符串转换为 Python 对象:

import json

res = json.loads(js)
res
{'name': 'Wes',
 'place_lived': ['United States', 'Spain', 'Germany'],
 'pet': None,
 'siblings': [{'name': 'Scott', 'age': 30, 'pets': ['Zeus', 'Zuko']},
  {'name': 'Katie', 'age': 38, 'pets': ['Sixes', 'Stache', 'Cisco']}]}

利用 json.dumps 可以将 Python 对象装换回 JSON 字符串。

JSON 数据

pandas.read_json 可以自动将 JSON 数据集按照指定次序转换为 Series 或 DataFrame,example.json 数据内容如下:

[
  {"a": 1, "b": 2, "c": 3},
  {"a": 4, "b": 5, "c": 6},
  {"a": 7, "b": 8, "c": 9},
]
df = pd.read_json('example.json')
df
#    a  b  c
# 0  1  2  3
# 1  4  5  6
# 2  7  8  9

同样,从 pandas 数据中导出 JSON 可以使用 Series 和 DataFrame 的 to_json 方法。

二进制数据

使用 Python 内建的 pickle 序列化模块进行二进制格式操作是存储数据(序列化)最高效,最方便的方法之一。pandas 对象拥有 to_pickle 方法可以将数据以 pickle 格式写入文件。使用 read_pickle 可以方便的读入 pickle 序列化后的文件。

HDF5 是一个备受好评的文件格式,用于存储大量的科学数组数据。每个 HDF5 文件可以存储多个数据集并支持元数据,与更简单的格式相比,HDF5 支持多种压缩模式的即时压缩,使得重复模式的数据可以更高效地存储。HDF5 适用于处理不适合在内存中存储的超大型数据,可以使你高效读写大型数组的一小块。

pandas 提供一个高阶的接口,可以简化 Series 和 DataFrame 的存储。HDFStore 类像字典一样工作并处理低级别细节:

df = pd.DataFrame({'a': np.random.randn(100)})
store = pd.HDFStore('tmp.h5')
store['obj1'] = df
store['obj1_col'] = df['a']
store
<class 'pandas.io.pytables.HDFStore'>
File path: tmp.h5

to_hdfread_hdf 函数提供了便捷的操作方法。

数据规整

分层索引

分层索引是 pandas 的重要特性,允许在一个轴向上拥有多个索引层级。分层索引提供了一种在更低维度的形式中处理更高维度数据的方式。

s = pd.Series(
  np.random.randn(9),
  index=[
    ['a', 'a', 'a',
     'b', 'b', 'c',
     'c', 'd', 'd'],
    [1, 2, 3,
     1, 3, 1,
     2, 2, 3]
  ]
)
s
a  1    0.637485
   2    0.208254
   3    0.073895
b  1   -1.206255
   3    1.515729
c  1   -0.290793
   2    0.378494
d  2    0.465555
   3   -0.787307
dtype: float64

通过分层索引对象,允许简洁地选择出数据的子集:

s['b']
1   -1.206255
3    1.515729
dtype: float64
s['b':'c']
b  1   -1.206255
   3    1.515729
c  1   -0.290793
   2    0.378494
dtype: float64
s.loc[['b', 'd']]
b  1   -1.206255
   3    1.515729
d  2    0.465555
   3   -0.787307
dtype: float64
s.loc[:, 2]
a    0.208254
c    0.378494
d    0.465555
dtype: float64

分层索引

分层索引在重塑数据和数组透视表等分组操作中扮演重要角色,例如:使用 unstack 方法将数据在 DataFrame 中重新排列:

s.unstack()
          1         2         3
a  0.637485  0.208254  0.073895
b -1.206255       NaN  1.515729
c -0.290793  0.378494       NaN
d       NaN  0.465555 -0.787307

unstack 的反操作是 stack

s.unstack().stack()

在 DataFrame 中,每个轴都可以拥有分层索引:

df = pd.DataFrame(
    np.arange(12).reshape((4, 3)),
    index=[['a', 'a', 'b', 'b'], [1, 2, 1, 2]],
    columns=[
        ['Ohio', 'Ohio', 'Colorado'],
        ['Green', 'Red', 'Green']]
)
df
     Ohio     Colorado
    Green Red    Green
a 1     0   1        2
  2     3   4        5
b 1     6   7        8
  2     9  10       11

分层索引

分层的层级可以有名称(可以是字符串或 Python 对象),如果层级有名称,这些名称会在控制台中显示:

df.index.names = ['key1', 'key2']
df.columns.names = ['state', 'color']
df
state      Ohio     Colorado
color     Green Red    Green
key1 key2                   
a    1        0   1        2
     2        3   4        5
b    1        6   7        8
     2        9  10       11

通过部分列索引,可以选出列中的组:

df['Ohio']
color      Green  Red
key1 key2            
a    1         0    1
     2         3    4
b    1         6    7
     2         9   10

重排序和层级排序

有时需要重新排列轴上的层级顺序,或者按照特定层级的值对数据进行排序。swaplevel 接受两个层级序号或层级名称,返回一个进行了层级变更的新对象:

df.swaplevel('key1', 'key2')
state      Ohio     Colorado
color     Green Red    Green
key2 key1                   
1    a        0   1        2
2    a        3   4        5
1    b        6   7        8
2    b        9  10       11

sort_index 只能在单一层级上对数据进行排序,在进行层级变换时,使用 sort_index 以使得结果按照层级进行字典排序:

df.sort_index(level=1)
state      Ohio     Colorado
color     Green Red    Green
key1 key2                   
a    1        0   1        2
b    1        6   7        8
a    2        3   4        5
b    2        9  10       11
df.swaplevel(0, 1).sort_index(level=0)
state      Ohio     Colorado
color     Green Red    Green
key2 key1                   
1    a        0   1        2
     b        6   7        8
2    a        3   4        5
     b        9  10       11

按层级进行汇总统计

DataFrame 和 Series 中很多统计性和汇总性统计可以通过 groupby 函数和 level 参数可以指定想要在某个特定的轴上进行聚合,按照层级在行上进行聚合:

df.groupby(level='key2').sum()
state  Ohio     Colorado
color Green Red    Green
key2                    
1         6   8       10
2        12  14       16

按照层级在列上进行聚合:

df.groupby(level='color', axis=1).sum()
color      Green  Red
key1 key2            
a    1         2    1
     2         8    4
b    1        14    7
     2        20   10

使用列进行索引

DataFrame 的 set_index 会生成一个新的 DataFrame,新的 DataFrame 使用一个或多个列作为索引:

df = pd.DataFrame({
    'a': range(7), 'b': range(7, 0, -1),
    'c': ['one', 'one', 'one', 'two', 'two', 'two', 'two'],
    'd': [0, 1, 2, 0, 1, 2, 3]})
df
   a  b    c  d
0  0  7  one  0
1  1  6  one  1
2  2  5  one  2
3  3  4  two  0
4  4  3  two  1
5  5  2  two  2
6  6  1  two  3
df.set_index(['c', 'd'], drop=False)
       a  b    c  d
c   d              
one 0  0  7  one  0
    1  1  6  one  1
    2  2  5  one  2
two 0  3  4  two  0
    1  4  3  two  1
    2  5  2  two  2
    3  6  1  two  3

默认情况下这些列会从 DataFrame 中移除,通过 drop=False 可以将其保留在 DataFrame 中。

数据库风格的连接

合并或连接操作通过一个或多个键来联合数据集,这些操作是关系型数据库的核心内容。pandas 中的 merge 函数主要用于将各种 join 操作应用到数据上:

df1 = pd.DataFrame({
    'key': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
    'data1': range(7)})
df1
  key  data1
0   b      0
1   b      1
2   a      2
3   c      3
4   a      4
5   a      5
6   b      6
df2 = pd.DataFrame({
    'key': ['a', 'b', 'd'],
    'data2': range(3)
})
df2
  key  data2
0   a      0
1   b      1
2   d      2

df1 的数据多个行的标签为 ab,而 df2key 列中每个值仅有一行。调用 merge 处理获取连接的对象:

pd.merge(df1, df2)
  key  data1  data2
0   b      0      1
1   b      1      1
2   a      2      0
3   a      4      0
4   a      5      0
5   b      6      1

上例中并没有指定在哪一列上进行连接,如果没有指定 merge 会自动将重叠的列名作为连接的键。

数据库风格的连接

可以显式地指定连接键:

pd.merge(df1, df2, on='key')
  key  data1  data2
0   b      0      1
1   b      1      1
2   a      2      0
3   a      4      0
4   a      5      0
5   b      6      1

如果对象的列名是不同的,可以分别为他们指定列名:

df3 = pd.DataFrame({
    'lkey': ['b', 'b', 'a', 'c', 'a', 'a', 'b'],
    'data1': range(7)
})
df4 = pd.DataFrame({
    'rkey': ['a', 'b', 'd'],
    'data2': range(3)
})
pd.merge(df3, df4, left_on='lkey', right_on='rkey')
  lkey  data1 rkey  data2
0    b      0    b      1
1    b      1    b      1
2    a      2    a      0
3    a      4    a      0
4    a      5    a      0
5    b      6    b      1

默认情况下 merge 做的是内连接 inner,其他可选项有左连接 left、右连接 right 和外连接 outer

数据库风格的连接

left = pd.DataFrame({
    'key1': ['foo', 'foo', 'bar'],
    'key2': ['one', 'two', 'one'], 'lval': [1, 2, 3]
})
right = pd.DataFrame({
    'key1': ['foo', 'foo', 'bar', 'bar'],
    'key2': ['one', 'one', 'one', 'two'],
    'rval': [4, 5, 6, 7]
})
pd.merge(left, right, on=['key1', 'key2'], how='outer')
  key1 key2  lval  rval
0  bar  one   3.0   6.0
1  bar  two   NaN   7.0
2  foo  one   1.0   4.0
3  foo  one   1.0   5.0
4  foo  two   2.0   NaN
pd.merge(left, right, on='key1')
  key1 key2_x  lval key2_y  rval
0  foo    one     1    one     4
1  foo    one     1    one     5
2  foo    two     2    one     4
3  foo    two     2    one     5
4  bar    one     3    one     6
5  bar    one     3    two     7
pd.merge(left, right, on='key1', suffixes=('_l', '_r'))
  key1 key2_l  lval key2_r  rval
0  foo    one     1    one     4
1  foo    one     1    one     5
2  foo    two     2    one     4
3  foo    two     2    one     5
4  bar    one     3    one     6
5  bar    one     3    two     7

数据库风格的连接

merge 函数的参数如下表所示:

参数 描述
left 合并时操作中左边的 DataFrame
right 合并时操作中右边的 DataFrame
how inner, outer, left, right 之一,默认为 inner
on 需要连接的列名,需要在两边的 DataFrame 中都存在。
left_on 左 DataFrame 中用作连接的键
right_on 右 DataFrame 中用作连接的键

数据库风格的连接

merge 函数的参数如下表所示:

参数 描述
left_index 使用 left 的行索引作为它的连接键(如果是 MultiIndex,则为多个键)
right_index 使用 right 的行索引作为它的连接键(如果是 MultiIndex,则为多个键)
sort 通过连接键按字母顺序对合并的数据进行排序,默认为 True
suffixes 在重叠情况下,添加到列名后的字符串元组,默认为 ('_x', '_y')
copy 如果为 False,则在某些特殊情况下避免将数据复制到结果数据结构中,默认情况下总是复制
indicator 添加一个特殊的列 _merge,指示每一行的来源。值将根据每行中连接数据的来源分别为 left_only, right_only, both

数据库风格的连接

DataFrame 有一个方便的 join 实例方法,用于按照索引合并。该方法也可以用于合并多个索引相同或相似但没有重叠列的 DataFrame 对象:

left = pd.DataFrame(
    [[1., 2.], [3., 4.], [5., 6.]],
    index=['a', 'c', 'e'], columns=['Ohio', 'Nevada'])
right = pd.DataFrame(
    [[7., 8.], [9., 10.], [11., 12.], [13., 14.]],
    index=['b', 'c', 'd', 'e'],
    columns=['Missouri', 'Alabama'])
left.join(right, how='outer')
   Ohio  Nevada  Missouri  Alabama
a   1.0     2.0       NaN      NaN
b   NaN     NaN       7.0      8.0
c   3.0     4.0       9.0     10.0
d   NaN     NaN      11.0     12.0
e   5.0     6.0      13.0     14.0

可以向 join 方法传递一个 DataFrame 列表:

another = pd.DataFrame(
    [[7.], [8.], [9.], [10.]],
    index=['a', 'c', 'e', 'f'],
    columns=['New York']
)
left.join([right, another], how='outer')
   Ohio  Nevada  Missouri  Alabama  New York
a   1.0     2.0       NaN      NaN       7.0
c   3.0     4.0       9.0     10.0       8.0
e   5.0     6.0      13.0     14.0       9.0
b   NaN     NaN       7.0      8.0       NaN
d   NaN     NaN      11.0     12.0       NaN
f   NaN     NaN       NaN      NaN      10.0

沿轴向连接

另一种数据组合操作可互换地称为拼接、绑定或堆叠。NumPy 的 concatenate 函数可以在 NumPy 数组上实现该该功能:

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

在 Series 和 DataFrame 等 pandas 对象上下文中,使用标记的轴可以进一步泛化数据连接。尤其是你还有需要考虑的事情:

  • 如果对象在其他轴上的索引不同,我们是否应该将不同的元素组合在这些轴上,还是只使用共享的值(交集)?
  • 连接的数据块是否需要在结果对象中被识别?
  • “连接轴”是否包含需要保存的数据?

pandas 的 concat 函数提供了一种一致性的方式来解决以上问题。

沿轴向连接

s1 = pd.Series([0, 1], index=['a', 'b'])
s2 = pd.Series([2, 3, 4], index=['c', 'd', 'e'])
s3 = pd.Series([5, 6], index=['f', 'g'])

调用 concat 方法会将值和索引连在一起:

pd.concat([s1, s2, s3])
a    0
b    1
c    2
d    3
e    4
f    5
g    6
dtype: int64

默认情况下,concat 方法是沿着 axis=0 的轴向生效的,生成另一个 Series。如果设置 axis=1,返回的结果则是一个 DataFrame:

pd.concat([s1, s2, s3], axis=1)
     0    1    2
a  0.0  NaN  NaN
b  1.0  NaN  NaN
c  NaN  2.0  NaN
d  NaN  3.0  NaN
e  NaN  4.0  NaN
f  NaN  NaN  5.0
g  NaN  NaN  6.0

沿轴向连接

拼接在一起的各部分无法在结果中区分是一个潜在的问题。假设你想在连接轴向上创建一个多层索引,可以使用 keys 参数来实现:

res = pd.concat(
    [s1, s1, s3], keys=['one', 'two', 'three'])
res
one    a    0
       b    1
two    a    0
       b    1
three  f    5
       g    6
dtype: int64
res.unstack()
         a    b    f    g
one    0.0  1.0  NaN  NaN
two    0.0  1.0  NaN  NaN
three  NaN  NaN  5.0  6.0

axis=1 连接 Series 时,keys 为 DataFrame 的列头:

pd.concat([s1, s2], axis=1, keys=['one', 'two'])
   one  two
a  0.0  NaN
b  1.0  NaN
c  NaN  2.0
d  NaN  3.0
e  NaN  4.0

沿轴向连接

对于 DataFrame 对象:

df1 = pd.DataFrame(
    np.arange(6).reshape((3, 2)),
    index=['a', 'b', 'c'], columns=['one', 'two'])
df2 = pd.DataFrame(
    np.arange(4).reshape((2, 2)),
    index=['a', 'c'], columns=['three', 'four'])
df1
   one  two
a    0    1
b    2    3
c    4    5
df2
   three  four
a      0     1
c      2     3
pd.concat([df1, df2], axis=1, keys=['l1', 'l2'])
   l1        l2     
  one two three four
a   0   1   0.0  1.0
b   2   3   NaN  NaN
c   4   5   2.0  3.0

如果传递的是对象的字典而不是列表的话,则字典的键会用于 keys 选项:

pd.concat({'l1': df1, 'l2': df2}, axis=1)
   l1        l2     
  one two three four
a   0   1   0.0  1.0
b   2   3   NaN  NaN
c   4   5   2.0  3.0

沿轴向连接

参数 描述
objs 需要连接的 pandas 对象列表或字典
axis 连接的轴向,默认是 0(沿着行方向)
join 用于指定连接方式(innerouter
keys 与要连接的对象关联的值,沿着连接轴形成分层索引。
levels 在键值传递时,该参数用于指定多层索引的层级
names 传入 keyslevels 时用于多层索引的层级名称
verify_integrity 检查连接对象中的新轴是否重复,如果是,则发生异常,默认为 False
ignore_index 不沿着连接轴保留索引,而产生一段新的索引

联合重叠数据

另一种数据联合场景,既不是合并,也不是连接。两个数据集的索引全部或部分重叠,考虑 NumPy 的 where 函数,这个函数可以进行面向数组的 if-else 等价操作:

a = pd.Series(
    [np.nan, 2.5, 0.0, np.nan],
    index=['d', 'c', 'b', 'a'])
b = pd.Series(
    [0, np.nan, 2, 3], index=['a', 'b', 'c', 'd'])
a
d    NaN
c    2.5
b    0.0
a    NaN
dtype: float64
b
a    0.0
b    NaN
c    2.0
d    3.0
dtype: float64
np.where(pd.isnull(a), b, a)
array([0. , 2.5, 0. , 3. ])

Series 的 combine_first 方法等基于上述操作:

b.combine_first(a)
a    0.0
b    0.0
c    2.0
d    3.0
dtype: float64

联合重叠数据

在 DataFrame 中,combine_first 逐列进行相同的操作,可以理解为利用传入的数值替换对象的缺失值:

df1 = pd.DataFrame({
    'a': [1., np.nan, 5., np.nan],
    'b': [np.nan, 2., np.nan, 6.],
    'c': range(2, 18, 4)
})
df2 = pd.DataFrame({
    'a': [5., 4., np.nan, 3., 7.],
    'b': [np.nan, 3., 4., 6., 8.]
})
df1
     a    b   c
0  1.0  NaN   2
1  NaN  2.0   6
2  5.0  NaN  10
3  NaN  6.0  14
df2
     a    b
0  5.0  NaN
1  4.0  3.0
2  NaN  4.0
3  3.0  6.0
4  7.0  8.0
df1.combine_first(df2)
     a    b     c
0  1.0  NaN   2.0
1  4.0  2.0   6.0
2  5.0  4.0  10.0
3  3.0  6.0  14.0
4  7.0  8.0   NaN

重塑和透视

多层索引在 DataFrame 中提供了一种一致性方式用于重排列数据,包含两个基础操作:

  • stack(堆叠),“旋转”或将列中的数据透视到行
  • unstack(拆堆),将行中的数据透视到列
df = pd.DataFrame(
    np.arange(6).reshape((2, 3)),
    index=pd.Index(['Ohio', 'Colorado'], name='state'),
    columns=pd.Index(['one', 'two', 'three'], name='num'))
df
num       one  two  three
state                    
Ohio        0    1      2
Colorado    3    4      5

利用 stack 方法将数据透视到行,产生新的 Series:

res = df.stack()
res
state     num  
Ohio      one      0
          two      1
          three    2
Colorado  one      3
          two      4
          three    5
dtype: int64

重塑和透视

如果层级中的所有值并未包含于每个子分组中,拆分可能会引入缺失值:

s1 = pd.Series(
    [0, 1, 2, 3], index=['a', 'b', 'c', 'd'])
s2 = pd.Series(
    [4, 5, 6], index=['c', 'd', 'e'])
df = pd.concat([s1, s2], keys=['one', 'two'])
df
one  a    0
     b    1
     c    2
     d    3
two  c    4
     d    5
     e    6
dtype: int64

默认情况下,堆叠会过滤出缺失值:

df.unstack()
       a    b    c    d    e
one  0.0  1.0  2.0  3.0  NaN
two  NaN  NaN  4.0  5.0  6.0
df.unstack().stack()
one  a    0.0
     b    1.0
     c    2.0
     d    3.0
two  c    4.0
     d    5.0
     e    6.0
dtype: float64

重塑和透视

在 DataFrame 中拆堆时,被拆堆的层级会变为结果中最低的层级:

df = pd.DataFrame(
    {'left': res, 'right': res + 5},
    columns=pd.Index(['left', 'right'], name='side'))
df
side            left  right
state    num               
Ohio     one       0      5
         two       1      6
         three     2      7
Colorado one       3      8
         two       4      9
         three     5     10

调用 stack 方法时,可以指明需要堆叠的轴向名称:

df.unstack('state')
side  left          right         
state Ohio Colorado  Ohio Colorado
num                               
one      0        3     5        8
two      1        4     6        9
three    2        5     7       10
df.unstack('state').stack('side')
state        Ohio  Colorado
num   side                 
one   left      0         3
      right     5         8
two   left      1         4
      right     6         9
three left      2         5
      right     7        10

重塑和透视

从一个多层索引序列中,可以使用 unstack 方法将数据重排列后放入一个 DataFrame 中:

res.unstack()
num       one  two  three
state                    
Ohio        0    1      2
Colorado    3    4      5

默认情况下,最内层是已拆堆的(与 stack 方法一样),传入层级序号或名称来拆分一个不同的层级:

res.unstack(0)
state  Ohio  Colorado
num                  
one       0         3
two       1         4
three     2         5
res.unstack('state')
state  Ohio  Colorado
num                  
one       0         3
two       1         4
three     2         5

重塑和透视

DataFrame 中的 pivotmelt 方法提供了将数据“由长到宽”和“由宽到长”变换的支持。

df = pd.DataFrame({
    'key': ['foo', 'bar', 'baz'] * 3,
    'var': ['A', 'A', 'A', 'B', 'B', 'B', 'C', 'C', 'C'],
    'val': range(9),
})
df
   key var  val
0  foo   A    0
1  bar   A    1
2  baz   A    2
3  foo   B    3
4  bar   B    4
5  baz   B    5
6  foo   C    6
7  bar   C    7
8  baz   C    8
pivoted = df.pivot(
    index='key', columns='var', values='val'
).reset_index()
pivoted
var  key  A  B  C
0    bar  1  4  7
1    baz  2  5  8
2    foo  0  3  6
pd.melt(pivoted, ['key'])
   key var  value
0  bar   A      1
1  baz   A      2
2  foo   A      0
3  bar   B      4
4  baz   B      5
5  foo   B      3
6  bar   C      7
7  baz   C      8
8  foo   C      6

感谢倾听

本作品采用 授权

版权所有 © 范叶亮 Leo Van