详解DataFrame、Series的replace方法

楔子

我们在处理数据的时候,经常会做一些替换的工作,我们会使用map或者apply,但是pandas中也提供了replace方法,会更加方便我们处理,我们来看一下。

Series的replace

import pandas as pd
import numpy as np

s = pd.Series(["a", 1, 1, "哈哈"])
# 将1替换成nan
print(s.replace(1, np.nan))
"""
0      a
1    NaN
2    NaN
3     哈哈
dtype: object
"""

我们看到还是很简单的,上面表示将Series对象里面的1全部换成nan。如果想替换多个值,该怎么办呢?

import pandas as pd

s = pd.Series([1, 1, 2, 3, 4, 5, 5])
# 所有的1换成'a'、3换成'b'、5换成'c'
print(s.replace([1, 3, 5], ['a', 'b', 'c']))
"""
0    a
1    a
2    2
3    b
4    4
5    c
6    c
dtype: object
"""

# 除此之外还可以通过字典的方式,如果是字典,那么只需要传递一个参数即可
# 将1换成'a'、将2换成'b'
print(s.replace({1: "a", 2: "b"}))
"""
0    a
1    a
2    b
3    3
4    4
5    5
6    5
dtype: object
"""

传递字典这个过程有点像map,但是map有一个机制,在某些时候是我们不希望看到的

print(s.map({1: "a", 2: "b"}))
"""
0      a
1      a
2      b
3    NaN
4    NaN
5    NaN
6    NaN
dtype: object
"""

我们看到如果不在指定的字典里面,那么自动都换成nan了。当然我们可以通过向map传递函数来解决,但是对于传递字典来说,对于不在字典里面的值,replace会保持原样,但是map会换成nan。

另外replace还支持以下形式替换:

import pandas as pd

s = pd.Series([1, 2, 3, 4, 5, 6])

# 如果通过列表传递,那么两个的列表的长度要一样,里面的元素一一对应
# 但如果第一个参数是列表,第二参数可以不是列表,比如这里:它表示将第一个列表里面的元素都换成100
print(s.replace([1, 4, 6], 100))
"""
0    100
1      2
2      3
3    100
4      5
5    100
"""

所以对于使用replace,元素替换有以下几种:

  • replace(a, b):将a换成b
  • replace([a1, a2, a3, ...], [b1, b2, b3, ...]):两个列表长度要一样,里面元素一一对应,这里是将a1换成b1、将a2换成b2、将a3换成b3...
  • replace([a1, a2, a3, ...], b):将第一个列表里面的所有元素a1、a2、a3...都换成b
  • replace({"a1": "b1", "a2": "b2"}):字典的方式就类似于map,很好理解。只是对于map来讲,不在字典里面的值会换成nan,而对于replace,不在的话则保持原样。

但是对于replace来讲,还有一个很关键的点,那就是None:

import pandas as pd

s = pd.Series([1, 2, 3, 4, 5, 6, 6])

# 我们将1 3 6换成None
print(s.replace([1, 3, 6], None))
"""
0    1
1    2
2    2
3    4
4    5
5    5
6    5
dtype: int64
"""
# 但是我们看到并没有得到期望的结果,这个在replace中是个特例
# 如果你想将某个值替换成None,等价于将该值 替换为 该值的前一个值
"""
我们上面将3换成None,等价于会换成3前面的值,也就是会把3换成2
同理6换成5,尽管两个6连接一起,但它们是个整体,前面是5,所以都换成5
而1的前面没有东西了,所以就是其本身
"""


s = pd.Series([1, 2, 3, 4, 5, 5, 6, 7])
# 如果在替换为None的时候想换成它的后一个值,可以将method指定为"bfill"
# 并且第二个参数其实默认就是为None的,所以第二个参数不传也是可以的
print(s.replace([1, 3, 6], method="bfill"))
"""
0    2
1    2
2    4
3    4
4    5
5    5
6    7
7    7
dtype: int64
"""

# 我们看到上面可以实现替换的效果,在局部范围内,将指定的值替换为它的上一个或者下一个值
# 不过虽然如此,可还是没有解决问题,因为本来就是想换成None的
# 如果真的想换成None,那就需要通过字典的方式
s = pd.Series([1, 2, 3, 4, 5, 5, 6, 7])
print(s.replace({1: None, 3: None, 6: None}))
"""
0    NaN
1    2.0
2    NaN
3    4.0
4    5.0
5    5.0
6    NaN
7    7.0
dtype: float64
"""
# 确实是换成了None,只不过pandas认为该Series对象的类型是整型,所以将None换成了NaN,然后将Series的类型变成了浮点型
# 在里面指定个字符串,就不是整型了
s = pd.Series([1, 2, 3, 4, "5", 5, 6, 7])
print(s.replace({1: None, 3: None, 6: None}))
# 我们看到的确实现了替换
"""
0    None
1       2
2    None
3       4
4       5
5       5
6    None
7       7
dtype: object
"""
# 当然字典的方式,不仅仅是None,其它的值也是可以的

对于整型来讲,上面替换的方式是足够了。当然字符串也可以像上面那样,只是对于字符串来讲,上面那些还不太够。

不像数字,在替换字符串的时候,显然会遇到模糊匹配的问题。没错,replace也是支持正则的。

import pandas as pd

s = pd.Series(["古明地觉", "古明地恋", "椎名真白", "芙兰朵露", "雨宫优子", "宫村宫子"])

# 如果希望第一个参数是正则匹配的话,那么要指定regex=True,默认为False
# 这里表示将包含"宫"字的值换成"悠久之翼"
print(s.replace(r".*宫.*", "悠久之翼", regex=True))
"""
0    古明地觉
1    古明地恋
2    椎名真白
3    芙兰朵露
4    悠久之翼
5    悠久之翼
dtype: object
"""

# 同理列表里面也是支持正则的
# 注意:如果是列表的话,在regex指定为True的前提下,第一个列表里面的元素必须是字符串、re.compile("...")、None,否则报错
# 但是第二个列表就没有要求了,就算不是列表也可以
print(s.replace([r".*古.*", r".*宫.*"], ["东方地灵殿", "悠久之翼"], regex=True))
"""
0    东方地灵殿
1    东方地灵殿
2     椎名真白
3     芙兰朵露
4     悠久之翼
5     悠久之翼
dtype: object
"""

# 同理字典的话,也是支持正则的,只要指定regex=True即可
# 当然字典里面的key也必须是字符串、re.compile("...")、None三者之一
print(s.replace({r".*古.*": "东方地灵殿"}, regex=True))
"""
0    东方地灵殿
1    东方地灵殿
2     椎名真白
3     芙兰朵露
4     雨宫优子
5     宫村宫子
dtype: object
"""

DataFrame的replace

弄清楚了Series的replace,DataFrame的replace就简单很多了,因为这两个老铁的replace里面的参数是一致的,所以用法是一样的。

import pandas as pd

df = pd.DataFrame({
    "a": [1, 2, 3, 1, 5],
    "b": [1, 2, 3, 1, 5],
    "c": ["古明地觉", "椎名真白", "坂上智代", "牧濑红莉栖", "雨宫优子"],
    "d": ["古明地觉", "椎名真白", "坂上智代", "牧濑红莉栖", "雨宫优子"]
})

# 将1替换成"aaa",这里是替换的所有列的所有的1
print(df.replace(1, "aaa"))
"""
     a    b      c      d
0  aaa  aaa   古明地觉   古明地觉
1    2    2   椎名真白   椎名真白
2    3    3   坂上智代   坂上智代
3  aaa  aaa  牧濑红莉栖  牧濑红莉栖
4    5    5   雨宫优子   雨宫优子
"""

# 将2替换为"aaa",3替换为"bbb"
print(df.replace([2, 3], ["aaa", "bbb"]))
"""
     a    b      c      d
0    1    1   古明地觉   古明地觉
1  aaa  aaa   椎名真白   椎名真白
2  bbb  bbb   坂上智代   坂上智代
3    1    1  牧濑红莉栖  牧濑红莉栖
4    5    5   雨宫优子   雨宫优子
"""

# 将2和3都替换为"aaa"
print(df.replace([2, 3], "aaa"))
"""
     a    b      c      d
0    1    1   古明地觉   古明地觉
1  aaa  aaa   椎名真白   椎名真白
2  aaa  aaa   坂上智代   坂上智代
3    1    1  牧濑红莉栖  牧濑红莉栖
4    5    5   雨宫优子   雨宫优子
"""

# 将"椎名真白"替换为"hello","雨宫优子"替换为123
print(df.replace({"椎名真白": "hello", "雨宫优子": 123}))
"""
   a  b      c         d
0  1  1   古明地觉     古明地觉
1  2  2    hello       hello
2  3  3   坂上智代     坂上智代
3  1  1   牧濑红莉栖   牧濑红莉栖
4  5  5    123         123
"""

我们看到和Series几乎没有区别,只不过DataFrame相当于多个Series,调用replace的时候会对每一个Series进行相应的操作。

import pandas as pd

df = pd.DataFrame({
    "a": [1, 2, 3, 1, 5],
    "b": [1, 2, 3, 1, 5],
    "c": ["古明地觉", "椎名真白", "坂上智代", "牧濑红莉栖", "雨宫优子"],
    "d": ["古明地觉", "椎名真白", "坂上智代", "牧濑红莉栖", "雨宫优子"]
})

print(df.replace([1, "椎名真白"], "@@@"))
"""
     a    b      c      d
0  @@@  @@@   古明地觉   古明地觉
1    2    2    @@@    @@@
2    3    3   坂上智代   坂上智代
3  @@@  @@@  牧濑红莉栖  牧濑红莉栖
4    5    5   雨宫优子   雨宫优子
"""

针对的确实每一列,那么问题来了,None怎么办?

import pandas as pd

df = pd.DataFrame({
    "a": [1, 2, 3, 1, 5],
    "b": [1, 2, 3, 1, 5],
    "c": ["古明地觉", "椎名真白", "坂上智代", "牧濑红莉栖", "雨宫优子"],
    "d": ["古明地觉", "椎名真白", "坂上智代", "牧濑红莉栖", "雨宫优子"]
})

# 默认变成上一个值
print(df.replace([1, "椎名真白"], None))
"""
   a  b      c      d
0  1  1   古明地觉   古明地觉
1  2  2   古明地觉   古明地觉
2  3  3   坂上智代   坂上智代
3  3  3  牧濑红莉栖  牧濑红莉栖
4  5  5   雨宫优子   雨宫优子
"""

# 指定method="bfill",变成下一个值
print(df.replace([1, "椎名真白"], None, method="bfill"))
"""
   a  b      c      d
0  2  2   古明地觉   古明地觉
1  2  2   坂上智代   坂上智代
2  3  3   坂上智代   坂上智代
3  5  5  牧濑红莉栖  牧濑红莉栖
4  5  5   雨宫优子   雨宫优子
"""

# 用字典可以替换为None
print(df.replace({1: None, "椎名真白": None}))
"""
      a     b      c      d
0  None  None   古明地觉   古明地觉
1     2     2   None   None
2     3     3   坂上智代   坂上智代
3  None  None  牧濑红莉栖  牧濑红莉栖
4     5     5   雨宫优子   雨宫优子
"""
# 我们看到居然列"a"、"b"中的None居然不是NaN,说明df使用replace之后变成了object

我们看到对None处理方式和Series是一样的,因为我们之前说了,这两个老铁的replace方法是通用的。

但是问题又来了,我们还是希望能够对指定了列进行操作,而不是针对所有的列,这个时候怎么做呢?

import pandas as pd

df = pd.DataFrame({
    "a": [1, 2, 3, 1, 5],
    "b": [1, 2, 3, 1, 5],
    "c": [1, 2, 3, 1, 5],
})

# 将3替换成8,针对的是所有的列
print(df.replace(3, 8))
"""
   a  b  c
0  1  1  1
1  2  2  2
2  8  8  8
3  1  1  1
4  5  5  5
"""

# 将a列中的3、b列中的2换成888
print(df.replace({"a": 3, "b": 2}, 888))
"""
     a    b  c
0    1    1  1
1    2  888  2
2  888    3  3
3    1    1  1
4    5    5  5
"""

# 将a列中的3替换成8
print(df.replace(3, {"a": 8}))
"""
   a  b  c
0  1  1  1
1  2  2  2
2  8  3  3
3  1  1  1
4  5  5  5
"""

# 将a列中的3替换成8,b列中的3替换成7,没有指定的列则不替换
print(df.replace(3, {"a": 8, "b": 7}))
"""
   a  b  c
0  1  1  1
1  2  2  2
2  8  7  3
3  1  1  1
4  5  5  5
"""

# 将a列中的3替换成333,将b列中的2替换成222
print(df.replace({"a": 3, "b": 2}, {"a": 333, "b": 222}))
"""
     a    b  c
0    1    1  1
1    2  222  2
2  333    3  3
3    1    1  1
4    5    5  5
"""

# 我们看一下它和Series的区别
# 我们说替换的时候,如果是字典的话,那么对于Series来讲,直接给第一个参数传个字典即可,不需要第二个参数
# 因为Series相当于是一个列,字典里面的k和v分别等同于"将哪些值替换"和"替换成哪些值",所以一个字典足够,我们不需要传递第二个参数
# 但是对于DataFrame来讲,如果只传递一个字典,那么相当于针对的所有列,字典的含义和给Series.replace传递的字典含义一样
# 但我们说DataFrame它有列的概念,所以当我们给第一个参数传递字典的时候,再给第二个参数传一个非空的值,那么第一个参数的字典含义就变了
"""
df.replace({"a": 2, "b": 3}):这表示将所有列中的为"a"的值换成2、为"b"的值换成3。此时的"a"和"b"只是一个普通的值
df.replace({"a": 2, "b": 3}, 666):这表示将"a"列中的2和"b"列中3都换成666。此时的"a"和"b"则不再是普通的值,而是列名
同理
df.replace(666, {"a": 2, "b": 3})则是将"a"列中的666换成2、"b"列中的666换成3

当然也可以给两个参数都传递字典:
df.replace({"a": 2, "b": 3}, {"a": 222, "b": 333}):表示将a列中的2换成222,b列中的3换成333

其实:
df.replace({"a": 2, "b": 3}, 666)可以认为是df.replace({"a": 2, "b": 3}, {"a": 666, "b": 666})
df.replace(666, {"a": 2, "b": 3})可以认为是df.replace({"a": 666, "b": 666}, {"a": 2, "b": 3})
"""

df = pd.DataFrame({"a": ['x', 'x', 'y', 'x']})
# 所以问题来了,这种写法可不可以
# 我将a列中的"x"全部换成None
print(df.replace({"a": "x"}, None))
"""
   a
0  x
1  x
2  y
3  x
"""
# 我们看到没有变化,这是因为第二个参数默认为None
# 传了个None等于没传,所以第一个参数里面的字典的含义和Series是一样的。其key不是列,而是普通的值
# 它表示将a列中值为"a"的元素换成"x",并不是将a列中值为"x"的元素换为None。
# 所以我们说如果第一个参数的字典的key若想表示字段,那么第二个参数要传递一个非空的值
# 所有有人想到了df.replace({"a": "x"}, {"a": None}),但答案是不行的,至于为什么?可以想想Series,默认会变成它的上一个值


# 但如果就想换成None呢
df = pd.DataFrame({"a": ['x', 'x', 'y', 'x'],
                   "b": ['x', 'x', 'y', 'z']})

print(df.replace({"a": {"x": None},
                  "b": {"x": 123, "y": None, "z": "哼哼哼"}
                  })
      )
"""
      a     b
0  None   123
1  None   123
2     y  None
3  None   哼哼哼
"""
# 我们看到即便是想指定DataFrame指定的列,我们依旧可以使用一个字典搞定,不用传递第二个参数

我们看到功能真的是很强大,可以进行各种替换。只是在替换为None的时候需要小心,如果替换为None,那么就只给第一个参数传递个字典{"值": "值"}就行,第二个参数不要传。如果是针对DataFrame的指定列,不是全局的所有列的话,那么就给第一个参数传递一个value为字典的字典{"列": {"值": "值"}}

如果不是替换为None,那么第一个参数和第二个参数怎么用都可以。

另外我们说了很多将值替换为None的情况,可原来的值就是None呢,我们怎么将None替换为别的值呢?这个时候,不建议是用replace,因为整型中的None会变成NaN,时间的None变成NaT,替换会不方便,这个时候建议使用fillna。

此外,替换多个值也是可以的。

import pandas as pd

df = pd.DataFrame({
    "a": [1, 2, 3, 1, 5],
    "b": [1, 2, 3, 1, 5],
    "c": [1, 2, 3, 1, 5],
})

# 将a中1和3替换成222,b中的2和5替换成444
print(df.replace({"a": [1, 3], "b": [2, 5]},
                 {"a": 222, "b": 444}))
"""
     a    b  c
0  222    1  1
1    2  444  2
2  222    3  3
3  222    1  1
4    5  444  5
"""

# 将a列中的1替换成111、3替换成333,将b列中的2替换成222、5替换成555
print(df.replace({"a": [1, 3], "b": [2, 5]},
                 {"a": [111, 333], "b": [222, 555]}))
"""
     a    b  c
0  111    1  1
1    2  222  2
2  333    3  3
3  111    1  1
4    5  555  5
"""

最后介绍一下正则,正则也是类似的

import pandas as pd

df = pd.DataFrame({
    "a": ["古明地觉", "椎名真白", "坂上智代", "牧濑红莉栖", "雨宫优子"],
    "b": ["古明地觉", "椎名真白", "坂上智代", "牧濑红莉栖", "雨宫优子"],
    "c": ["古明地觉", "椎名真白", "坂上智代", "牧濑红莉栖", "雨宫优子"],
})

# 全局
print(df.replace(r".*古.*", "古明地觉"[:: -1], regex=True))
"""
       a      b      c
0   觉地明古   觉地明古   觉地明古
1   椎名真白   椎名真白   椎名真白
2   坂上智代   坂上智代   坂上智代
3  牧濑红莉栖  牧濑红莉栖  牧濑红莉栖
4   雨宫优子   雨宫优子   雨宫优子
"""
# 全局
print(df.replace([r".*古.*", r".*宫.*"],
                 ["古明地觉"[:: -1], "雨宫优子"[:: -1]],
                 regex=True))
"""
       a      b      c
0   觉地明古   觉地明古   觉地明古
1   椎名真白   椎名真白   椎名真白
2   坂上智代   坂上智代   坂上智代
3  牧濑红莉栖  牧濑红莉栖  牧濑红莉栖
4   子优宫雨   子优宫雨   子优宫雨
"""
# 全局
print(df.replace([r".*古.*", r".*宫.*"], "美少女", regex=True))
"""
       a      b      c
0    美少女    美少女    美少女
1   椎名真白   椎名真白   椎名真白
2   坂上智代   坂上智代   坂上智代
3  牧濑红莉栖  牧濑红莉栖  牧濑红莉栖
4    美少女    美少女    美少女
"""
# 指定的列
print(df.replace({"a": r".*古.*", "b": r".*古.*"},
                 {"a": "小小小小", "b": "五五五五"},
                 regex=True))
"""
       a      b      c
0   小小小小   五五五五   古明地觉
1   椎名真白   椎名真白   椎名真白
2   坂上智代   坂上智代   坂上智代
3  牧濑红莉栖  牧濑红莉栖  牧濑红莉栖
4   雨宫优子   雨宫优子   雨宫优子
"""
# 指定的列
print(df.replace({"a": [r".*古.*", r".*宫.*"], "b": [r".*古.*", r".*宫.*"]},
                 {"a": ["小小小小", "天天天天"], "b": ["五五五五", "使使使使"]},
                 regex=True))
"""
       a      b      c
0   小小小小   五五五五   古明地觉
1   椎名真白   椎名真白   椎名真白
2   坂上智代   坂上智代   坂上智代
3  牧濑红莉栖  牧濑红莉栖  牧濑红莉栖
4   天天天天   使使使使   雨宫优子
"""
# 当然这种嵌套字典的方式也是可以的,结果和上面一样
print(df.replace(
    {
        "a": {r".*古.*": "小小小小", r".*宫.*": "天天天天"},
        "b": {r".*古.*": "五五五五", r".*宫.*": "使使使使"}
    },
    regex=True))
"""
       a      b      c
0   小小小小   五五五五   古明地觉
1   椎名真白   椎名真白   椎名真白
2   坂上智代   坂上智代   坂上智代
3  牧濑红莉栖  牧濑红莉栖  牧濑红莉栖
4   天天天天   使使使使   雨宫优子
"""

我们发现,这个replace真的是强大的无与伦比,处理数据真的非常方便。还是那句话,只要不是将值变为None,那么两个参数随便用。但是一旦要变为None,那么建议通过只给第一个参数传递字典、第二个参数不传递的方式进行处理。

总结

到这里,我们算是介绍完了replace的用法,replace在数据替换确实是一大瑞士军刀。举个我工作中的栗子,我们经常会从Excel或者csv、数据库里面读取数据。而有的列的数据明显是时间格式,但是因为包含空格什么的,导致该列不是时间类型,要是使用pd.to_datetime强转也会报错(当然可以在pd.to_datetime里面,添加一个参数error="coerce",这样不符合时间格式的会自动变成NaT),因为空格或者空字符串不符合时间格式。如果你经常处理数据的话,那么你应该会遇到这个问题

而一般情况,大家可能都这么做过

# 假设该列的数据都是时间格式,不符合时间格式的值只包含空格
df["date"] = df["date"].map(lambda x: None if pd.not(na) and x.strip() == "" else x)
df["date"] = pd.to_datetime(df["date"])

# 或者
from dateutil import parser
df["date"] = df["date"].map(lambda x: None if pd.not(na) and x.strip() == "" else parser.parse(x))

如果只有一列时间格式的数据,上面的方法还是可以接受的,但我们需要很多列都要变为时间类型呢?所以这个时候replace就派上用场了

import pandas as pd

df = pd.DataFrame({
    "a": ["2018-1-1", " ", "", "	    ", "2018-3-8"],
    "b": ["      ", "  ", "", "2018-12-11", "2020-1-5"],
    "c": [" ", "", "    ", " ", "   "],
})
# 因为是变为None,所以不可以用这种方式:df.replace(r"^s*$", None, regex=True)
# 而是要用下面的字典的方式,如果变成其它的值,那么两种方法都能用,至于原因我们上面已经说过了
print(df.replace({r"^s*$": None}, regex=True))
"""
          a           b     c
0  2018-1-1        None  None
1      None        None  None
2      None        None  None
3      None  2018-12-11  None
4  2018-3-8    2020-1-5  None
"""
# 注意一下正则,这个正则就类似于re.search(),只要能匹配上,那么就会进行替换
# 比如df.replace({r"2": None}),那么会把所有符合日期格式的字符串都换成None,因为上面的日期字符串都是包含2的
# 所以是只要能匹配上、即使是局部匹配,只要是匹配上了就替换,所以我们上面的正则是r"^s*$",要求你的开头和结尾都必须是空格才行
# 如果不加^和$会有什么后果,我们来看看
print(df.replace({r"s*": None}, regex=True))
"""
      a     b     c
0  None  None  None
1  None  None  None
2  None  None  None
3  None  None  None
4  None  None  None
"""
# 我们看到全部变成了None,因为我们是s*,*要求可以匹配0个
import re
print(re.search(r"s*", "2018-1-1"))  # <re.Match object; span=(0, 0), match=''>
# 我们看到结果不为None,匹配上了空串。返回的不是None,那么结果就为真,就会替换
# 所以为了保证不会错误替换,建议正则匹配的时候,最好严谨一些

希望这个replace方法能给的数据处理带来方便

原文地址:https://www.cnblogs.com/traditional/p/12577839.html