楔子
我们在处理数据的时候,经常会做一些替换的工作,我们会使用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方法能给的数据处理带来方便