Pandas中文官档~基础用法4

重置索引与更换标签

reindex() 是 pandas 里实现数据对齐的基本方法,该方法执行几乎所有功能都要用到的标签对齐功能。 reindex 指的是沿着指定轴,让数据与给定的一组标签进行匹配。该功能完成以下几项操作:

  • 让现有数据匹配一组新标签,并重新排序;
  • 在无数据但有标签的位置插入缺失值(NA)标记;
  • 如果指定,则按逻辑填充无标签的数据,该操作多见于时间序列数据。

示例如下:

In [196]: s = pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'])

In [197]: s
Out[197]: 
a    1.695148
b    1.328614
c    1.234686
d   -0.385845
e   -1.326508
dtype: float64

In [198]: s.reindex(['e', 'b', 'f', 'd'])
Out[198]: 
e   -1.326508
b    1.328614
f         NaN
d   -0.385845
dtype: float64

本例中,原 Series 里没有标签 f ,因此,输出结果里 f 对应的值为 NaN

DataFrame 支持同时 reindex 索引与列:

In [199]: df
Out[199]: 
        one       two     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [200]: df.reindex(index=['c', 'f', 'b'], columns=['three', 'two', 'one'])
Out[200]: 
      three       two       one
c  1.227435  1.478369  0.695246
f       NaN       NaN       NaN
b -0.050390  1.912123  0.343054

reindex 还支持 axis 关键字:

In [201]: df.reindex(['c', 'f', 'b'], axis='index')
Out[201]: 
        one       two     three
c  0.695246  1.478369  1.227435
f       NaN       NaN       NaN
b  0.343054  1.912123 -0.050390

注意:不同对象可以共享 Index 包含的轴标签。比如,有一个 Series,还有一个 DataFrame,可以执行下列操作:

In [202]: rs = s.reindex(df.index)

In [203]: rs
Out[203]: 
a    1.695148
b    1.328614
c    1.234686
d   -0.385845
dtype: float64

In [204]: rs.index is df.index
Out[204]: True

这里指的是,重置后,Series 的索引与 DataFrame 的索引是同一个 Python 对象。

0.21.0 版新增

DataFrame.reindex() 还支持 “轴样式”调用习语,可以指定单个 labels 参数,并指定应用于哪个 axis

In [205]: df.reindex(['c', 'f', 'b'], axis='index')
Out[205]: 
        one       two     three
c  0.695246  1.478369  1.227435
f       NaN       NaN       NaN
b  0.343054  1.912123 -0.050390

In [206]: df.reindex(['three', 'two', 'one'], axis='columns')
Out[206]: 
      three       two       one
a       NaN  1.772517  1.394981
b -0.050390  1.912123  0.343054
c  1.227435  1.478369  0.695246
d -0.613172  0.279344       NaN

::: tip 注意

多重索引与高级索引介绍了怎样用更简洁的方式重置索引。

:::

::: tip 注意

编写注重性能的代码时,最好花些时间深入理解 reindex预对齐数据后,操作会更快。两个未对齐的 DataFrame 相加,后台操作会执行 reindex。探索性分析时很难注意到这点有什么不同,这是因为 reindex 已经进行了高度优化,但需要注重 CPU 周期时,显式调用 reindex 还是有一些影响的。

:::

重置索引,并与其它对象对齐

提取一个对象,并用另一个具有相同标签的对象 reindex 该对象的轴。这种操作的语法虽然简单,但未免有些啰嗦。这时,最好用 reindex_like() 方法,这是一种既有效,又简单的方式:

In [207]: df2
Out[207]: 
        one       two
a  1.394981  1.772517
b  0.343054  1.912123
c  0.695246  1.478369

In [208]: df3
Out[208]: 
        one       two
a  0.583888  0.051514
b -0.468040  0.191120
c -0.115848 -0.242634

In [209]: df.reindex_like(df2)
Out[209]: 
        one       two
a  1.394981  1.772517
b  0.343054  1.912123
c  0.695246  1.478369

align 对齐多个对象

align() 方法是对齐两个对象最快的方式,该方法支持 join 参数(请参阅 joining 与 merging):

  • join='outer':使用两个对象索引的合集,默认值
  • join='left':使用左侧调用对象的索引
  • join='right':使用右侧传递对象的索引
  • join='inner':使用两个对象索引的交集

该方法返回重置索引后的两个 Series 元组:

In [210]: s = pd.Series(np.random.randn(5), index=['a', 'b', 'c', 'd', 'e'])

In [211]: s1 = s[:4]

In [212]: s2 = s[1:]

In [213]: s1.align(s2)
Out[213]: 
(a   -0.186646
 b   -1.692424
 c   -0.303893
 d   -1.425662
 e         NaN
 dtype: float64, a         NaN
 b   -1.692424
 c   -0.303893
 d   -1.425662
 e    1.114285
 dtype: float64)

In [214]: s1.align(s2, join='inner')
Out[214]: 
(b   -1.692424
 c   -0.303893
 d   -1.425662
 dtype: float64, b   -1.692424
 c   -0.303893
 d   -1.425662
 dtype: float64)

In [215]: s1.align(s2, join='left')
Out[215]: 
(a   -0.186646
 b   -1.692424
 c   -0.303893
 d   -1.425662
 dtype: float64, a         NaN
 b   -1.692424
 c   -0.303893
 d   -1.425662
 dtype: float64)

默认条件下, join 方法既应用于索引,也应用于列:

In [216]: df.align(df2, join='inner')
Out[216]: 
(        one       two
 a  1.394981  1.772517
 b  0.343054  1.912123
 c  0.695246  1.478369,         one       two
 a  1.394981  1.772517
 b  0.343054  1.912123
 c  0.695246  1.478369)

align 方法还支持 axis 选项,用来指定要对齐的轴:

In [217]: df.align(df2, join='inner', axis=0)
Out[217]: 
(        one       two     three
 a  1.394981  1.772517       NaN
 b  0.343054  1.912123 -0.050390
 c  0.695246  1.478369  1.227435,         one       two
 a  1.394981  1.772517
 b  0.343054  1.912123
 c  0.695246  1.478369)

如果把 Series 传递给 DataFrame.align(),可以用 axis 参数选择是在 DataFrame 的索引,还是列上对齐两个对象:

In [218]: df.align(df2.iloc[0], axis=1)
Out[218]: 
(        one     three       two
 a  1.394981       NaN  1.772517
 b  0.343054 -0.050390  1.912123
 c  0.695246  1.227435  1.478369
 d       NaN -0.613172  0.279344, one      1.394981
 three         NaN
 two      1.772517
 Name: a, dtype: float64)
方法 动作
pad / ffill 先前填充
bfill / backfill 向后填充
nearest 从最近的索引值填充

下面用一个简单的 Series 展示 fill 方法:

In [219]: rng = pd.date_range('1/3/2000', periods=8)

In [220]: ts = pd.Series(np.random.randn(8), index=rng)

In [221]: ts2 = ts[[0, 3, 6]]

In [222]: ts
Out[222]: 
2000-01-03    0.183051
2000-01-04    0.400528
2000-01-05   -0.015083
2000-01-06    2.395489
2000-01-07    1.414806
2000-01-08    0.118428
2000-01-09    0.733639
2000-01-10   -0.936077
Freq: D, dtype: float64

In [223]: ts2
Out[223]: 
2000-01-03    0.183051
2000-01-06    2.395489
2000-01-09    0.733639
dtype: float64

In [224]: ts2.reindex(ts.index)
Out[224]: 
2000-01-03    0.183051
2000-01-04         NaN
2000-01-05         NaN
2000-01-06    2.395489
2000-01-07         NaN
2000-01-08         NaN
2000-01-09    0.733639
2000-01-10         NaN
Freq: D, dtype: float64

In [225]: ts2.reindex(ts.index, method='ffill')
Out[225]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05    0.183051
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08    2.395489
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

In [226]: ts2.reindex(ts.index, method='bfill')
Out[226]: 
2000-01-03    0.183051
2000-01-04    2.395489
2000-01-05    2.395489
2000-01-06    2.395489
2000-01-07    0.733639
2000-01-08    0.733639
2000-01-09    0.733639
2000-01-10         NaN
Freq: D, dtype: float64

In [227]: ts2.reindex(ts.index, method='nearest')
Out[227]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05    2.395489
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08    0.733639
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

上述操作要求索引按递增或递减排序

注意:除了 method='nearest',用 fillnainterpolate 也能实现同样的效果:

In [228]: ts2.reindex(ts.index).fillna(method='ffill')
Out[228]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05    0.183051
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08    2.395489
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

如果索引不是按递增或递减排序,reindex() 会触发 ValueError 错误。fillna()interpolate() 则不检查索引的排序。

重置索引填充的限制

limittolerance 参数可以控制 reindex 的填充操作。limit 限定了连续匹配的最大数量:

In [229]: ts2.reindex(ts.index, method='ffill', limit=1)
Out[229]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05         NaN
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08         NaN
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

反之,tolerance 限定了索引与索引器值之间的最大距离:

In [230]: ts2.reindex(ts.index, method='ffill', tolerance='1 day')
Out[230]: 
2000-01-03    0.183051
2000-01-04    0.183051
2000-01-05         NaN
2000-01-06    2.395489
2000-01-07    2.395489
2000-01-08         NaN
2000-01-09    0.733639
2000-01-10    0.733639
Freq: D, dtype: float64

注意:索引为 DatetimeIndexTimedeltaIndexPeriodIndex 时,tolerance 会尽可能将这些索引强制转换为 Timedelta,这里要求用户用恰当的字符串设定 tolerance 参数。

去掉轴上的标签

drop() 函数与 reindex 经常配合使用,该函数用于删除轴上的一组标签:

In [231]: df
Out[231]: 
        one       two     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [232]: df.drop(['a', 'd'], axis=0)
Out[232]: 
        one       two     three
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435

In [233]: df.drop(['one'], axis=1)
Out[233]: 
        two     three
a  1.772517       NaN
b  1.912123 -0.050390
c  1.478369  1.227435
d  0.279344 -0.613172

注意:下面的代码可以运行,但不够清晰:

In [234]: df.reindex(df.index.difference(['a', 'd']))
Out[234]: 
        one       two     three
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435

重命名或映射标签

rename() 方法支持按不同的轴基于映射(字典或 Series)调整标签。

In [235]: s
Out[235]: 
a   -0.186646
b   -1.692424
c   -0.303893
d   -1.425662
e    1.114285
dtype: float64

In [236]: s.rename(str.upper)
Out[236]: 
A   -0.186646
B   -1.692424
C   -0.303893
D   -1.425662
E    1.114285
dtype: float64

如果调用的是函数,该函数在处理标签时,必须返回一个值,而且生成的必须是一组唯一值。此外,rename() 还可以调用字典或 Series。

In [237]: df.rename(columns={'one': 'foo', 'two': 'bar'},
   .....:           index={'a': 'apple', 'b': 'banana', 'd': 'durian'})
   .....: 
Out[237]: 
             foo       bar     three
apple   1.394981  1.772517       NaN
banana  0.343054  1.912123 -0.050390
c       0.695246  1.478369  1.227435
durian       NaN  0.279344 -0.613172

pandas 不会重命名标签未包含在映射里的列或索引。注意,映射里多出的标签不会触发错误。

0.21.0 版新增

DataFrame.rename() 还支持“轴式”习语,用这种方式可以指定单个 mapper,及执行映射的 axis

In [238]: df.rename({'one': 'foo', 'two': 'bar'}, axis='columns')
Out[238]: 
        foo       bar     three
a  1.394981  1.772517       NaN
b  0.343054  1.912123 -0.050390
c  0.695246  1.478369  1.227435
d       NaN  0.279344 -0.613172

In [239]: df.rename({'a': 'apple', 'b': 'banana', 'd': 'durian'}, axis='index')
Out[239]: 
             one       two     three
apple   1.394981  1.772517       NaN
banana  0.343054  1.912123 -0.050390
c       0.695246  1.478369  1.227435
durian       NaN  0.279344 -0.613172

rename() 方法还提供了 inplace 命名参数,默认为 False,并会复制底层数据。inplace=True 时,会直接在原数据上重命名。

0.18.0 版新增

rename() 还支持用标量或列表更改 Series.name 属性。

In [240]: s.rename("scalar-name")
Out[240]: 
a   -0.186646
b   -1.692424
c   -0.303893
d   -1.425662
e    1.114285
Name: scalar-name, dtype: float64

0.24.0 版新增

rename_axis() 方法支持指定 多重索引 名称,与标签相对应。

In [241]: df = pd.DataFrame({'x': [1, 2, 3, 4, 5, 6],
   .....:                    'y': [10, 20, 30, 40, 50, 60]},
   .....:                   index=pd.MultiIndex.from_product([['a', 'b', 'c'], [1, 2]],
   .....:                   names=['let', 'num']))
   .....: 

In [242]: df
Out[242]: 
         x   y
let num       
a   1    1  10
    2    2  20
b   1    3  30
    2    4  40
c   1    5  50
    2    6  60

In [243]: df.rename_axis(index={'let': 'abc'})
Out[243]: 
         x   y
abc num       
a   1    1  10
    2    2  20
b   1    3  30
    2    4  40
c   1    5  50
    2    6  60

In [244]: df.rename_axis(index=str.upper)
Out[244]: 
         x   y
LET NUM       
a   1    1  10
    2    2  20
b   1    3  30
    2    4  40
c   1    5  50
    2    6  60

迭代

pandas 对象基于类型进行迭代操作。Series 迭代时被视为数组,基础迭代生成值。DataFrame 则遵循字典式习语,用对象的 key 实现迭代操作。

简言之,基础迭代(for i in object)生成:

  • Series :值
  • DataFrame:列标签

例如,DataFrame 迭代时输出列名:

In [245]: df = pd.DataFrame({'col1': np.random.randn(3),
   .....:                    'col2': np.random.randn(3)}, index=['a', 'b', 'c'])
   .....: 

In [246]: for col in df:
   .....:     print(col)
   .....: 
col1
col2

Pandas 对象还支持字典式的 items() 方法,通过键值对迭代。

用下列方法可以迭代 DataFrame 里的行:

  • iterrows():把 DataFrame 里的行当作 (index, Series)对进行迭代。该操作把行转为 Series,同时改变数据类型,并对性能有影响。

  • itertuples() 把 DataFrame 的行当作值的命名元组进行迭代。该操作比 iterrows() 快的多,建议尽量用这种方法迭代 DataFrame 的值。

::: danger 警告

Pandas 对象迭代的速度较慢。大部分情况下,没必要对行执行迭代操作,建议用以下几种替代方式:

  • 矢量化:很多操作可以用内置方法或 Numpy 函数,布尔索引……
  • 调用的函数不能在完整的 DataFrame / Series 上运行时,最好用 apply(),不要对值进行迭代操作。请参阅函数应用文档。
  • 如果必须对值进行迭代,请务必注意代码的性能,建议在 cython 或 numba 环境下实现内循环。参阅增强性能一节,查看这种操作方法的示例。

:::

::: danger 警告

永远不要修改迭代的内容,这种方式不能确保所有操作都能正常运作。基于数据类型,迭代器返回的是复制(copy)的结果,不是视图(view),这种写入可能不会生效!

下例中的赋值就不会生效:

In [247]: df = pd.DataFrame({'a': [1, 2, 3], 'b': ['a', 'b', 'c']})

In [248]: for index, row in df.iterrows():
.....:     row['a'] = 10
.....: 

In [249]: df
Out[249]: 
a  b
0  1  a
1  2  b
2  3  c

:::

项目(items)

与字典型接口类似,items() 通过键值对进行迭代:

  • Series:(Index,标量值)对
  • DataFrame:(列,Series)对

示例如下:

In [250]: for label, ser in df.items():
   .....:     print(label)
   .....:     print(ser)
   .....: 
a
0    1
1    2
2    3
Name: a, dtype: int64
b
0    a
1    b
2    c
Name: b, dtype: object

iterrows

iterrows() 迭代 DataFrame 或 Series 里的每一行数据。这个操作返回一个迭代器,生成索引值及包含每行数据的 Series:

In [251]: for row_index, row in df.iterrows():
   .....:     print(row_index, row, sep='\n')
   .....: 
0
a    1
b    a
Name: 0, dtype: object
1
a    2
b    b
Name: 1, dtype: object
2
a    3
b    c
Name: 2, dtype: object

::: tip 注意

iterrows() 返回的是 Series 里的每一行数据,该操作不会保留每行数据的数据类型,因为数据类型是通过 DataFrame 的列界定的。

示例如下:

In [252]: df_orig = pd.DataFrame([[1, 1.5]], columns=['int', 'float'])

In [253]: df_orig.dtypes
Out[253]: 
int        int64
float    float64
dtype: object

In [254]: row = next(df_orig.iterrows())[1]

In [255]: row
Out[255]: 
int      1.0
float    1.5
Name: 0, dtype: float64

row 里的值以 Series 形式返回,并被转换为浮点数,原始的整数值则在列 X:

In [256]: row['int'].dtype
Out[256]: dtype('float64')

In [257]: df_orig['int'].dtype
Out[257]: dtype('int64')

要想在行迭代时保存数据类型,最好用 itertuples(),这个函数返回值的命名元组,总的来说,该操作比 iterrows() 速度更快。

:::

下例展示了怎样转置 DataFrame:

In [258]: df2 = pd.DataFrame({'x': [1, 2, 3], 'y': [4, 5, 6]})

In [259]: print(df2)
   x  y
0  1  4
1  2  5
2  3  6

In [260]: print(df2.T)
   0  1  2
x  1  2  3
y  4  5  6

In [261]: df2_t = pd.DataFrame({idx: values for idx, values in df2.iterrows()})

In [262]: print(df2_t)
   0  1  2
x  1  2  3
y  4  5  6

itertuples

itertuples() 方法返回为 DataFrame 里每行数据生成命名元组的迭代器。该元组的第一个元素是行的索引值,其余的值则是行的值。

示例如下:

In [263]: for row in df.itertuples():
   .....:     print(row)
   .....: 
Pandas(Index=0, a=1, b='a')
Pandas(Index=1, a=2, b='b')
Pandas(Index=2, a=3, b='c')

该方法不会把行转换为 Series,只是返回命名元组里的值。itertuples() 保存值的数据类型,而且比 iterrows() 快。

::: tip 注意

包含无效 Python 识别符的列名、重复的列名及以下划线开头的列名,会被重命名为位置名称。如果列数较大,比如大于 255 列,则返回正则元组。

:::

呆鸟云:“翻译不易,五天翻译,四处求证,三番校稿,二时排版,只求一秒点赞。”

你可能感兴趣的:(Pandas中文官档~基础用法4)