今天我们看一下Pandas中的数据转换,话不多说直接开始🎇
目录
一、⭐️apply函数应用
apply是一个自由度很高的函数
对于Series,它可以迭代每一列的值操作:
二、⭐️矢量化字符串
为什么要用str属性
替换和分割
提取子串
提取第一个匹配的子串
测试是否包含子串
生成哑变量
⭐️方法摘要
函数应用
import pandas as pd
import numpy as np
一、⭐️apply函数应用
apply是一个自由度很高的函数
对于Series,它可以迭代每一列的值操作:
df = pd.read_csv('data/table.csv')
df.head()
df['Math'].apply(lambda x:str(x)+'!').head() #可以使用lambda表达式,也可以使用函数
💥对于DataFrame,它在默认axis=0下可以迭代每一个列操作:
# def test(x):
# print(x)
# return x
# df.apply(test)#axis=0
# df.apply(lambda x:x.apply(lambda x:str(x)+'!')).head() #这是一个稍显复杂的例子,有利于理解apply的功能
temp_data = df[["Height", "Weight", "Math"]]
# temp_data
# 生成一个表格,每列是原来列的最大值,最小值,以及均值
def transfor(x):
# x是Series
result = pd.Series()
result["max"] = x.max()
result["min"] = x.min()
result["avg"] = x.mean()
return result
temp_data.apply(transfor, axis=0)
# 按列来传入,一列就是一个x
def transfor(x):
# x -> series
bmi = x["Weight"]/(x["Height"]/100)**2
x["bmi"] = bmi
return x
temp_data.apply(transfor, axis=1)# BMI = # apply
Pandas中的axis参数=0时,永远表示的是处理方向而不是聚合方向,当axis='index'或=0时,对列迭代对行聚合,行即为跨列,axis=1同理 💥
二、⭐️矢量化字符串
为什么要用str属性
文本数据也就是我们常说的字符串,Pandas 为 Series 提供了 str
属性,通过它可以方便的对每个元素进行操作。
index = pd.Index(data=["Tom", "Bob", "Mary", "James", "Andy", "Alice"], name="name")
data = {
"age": [18, 30, np.nan, 40, np.nan, 30],
"city": ["Bei Jing ", "Shang Hai ", "Guang Zhou", "Shen Zhen", np.nan, " "],
"sex": [None, "male", "female", "male", np.nan, "unknown"],
"birth": ["2000-02-10", "1988-10-17", None, "1978-08-08", np.nan, "1988-10-17"]
}
user_info = pd.DataFrame(data=data, index=index)
# 将出生日期转为时间戳
user_info["birth"] = pd.to_datetime(user_info.birth)
user_info
💯在对 Series 中每个元素处理时,我们可以使用apply
方法。
💯比如,我想要将每个城市都转为小写,可以使用如下的方式。
user_info.city.map(lambda x: x.lower())
AttributeError: 'float' object has no attribute 'lower'
💯错误原因是因为 float 类型的对象没有 lower 属性。这是因为缺失值(np.nan)属于float 类型。这时候我们的str
属性操作来了,来看看如何使用吧~
# 将文本转为小写
user_info.city.str.lower()
可以看到,通过 `str` 属性来访问之后用到的方法名与 Python 内置的字符串的方法名一样。并且能够自动排除缺失值。我们再来试试其他一些方法。例如,统计每个字符串的长度。
user_info.city.str.len()
替换和分割
使用 .srt
属性也支持替换与分割操作。
先来看下替换操作,例如:将空字符串替换成下划线。
user_info.city.str.replace(" ", "_")
🏹replace
方法还支持正则表达式,例如将所有开头为 S
的城市替换为空字符串。
user_info.city.str.replace("^S.*", " ")
🏹再来看下分割操作,例如根据空字符串来分割某一列
user_info.city.str.split(" ")
🏹分割列表中的元素可以使用 get
或 []
符号进行访问:
user_info.city.str.split(" ").str.get(1)
🏹设置参数 expand=True
可以轻松扩展此项以返回 DataFrame。
user_info.city.str.split(" ", expand=True)
提取子串
既然是在操作字符串,很自然,你可能会想到是否可以从一个长的字符串中提取出子串。答案是可以的。
提取第一个匹配的子串
extract
方法接受一个正则表达式并至少包含一个捕获组,指定参数 expand=True
可以保证每次都返回 DataFrame。
例如,现在想要匹配空字符串前面的所有的字母,可以使用如下操作:
user_info.city.str.extract("(\w+)\s+", expand=True)
如果使用多个组提取正则表达式会返回一个 DataFrame,每个组只有一列。
例如,想要匹配出空字符串前面和后面的所有字母,操作如下:
user_info.city.str.extract("(\w+)\s+(\w+)", expand=True)
测试是否包含子串
除了可以匹配出子串外,我们还可以使用 contains
来测试是否包含子串。例如,想要测试城市是否包含子串 “Zh”。
user_info.city.str.contains("Zh")
当然了,正则表达式也是支持的。例如,想要测试是否是以字母 “S” 开头。
user_info.city.str.contains("^S")
生成哑变量
这是一个神奇的功能,通过 get_dummies
方法可以将字符串转为哑变量,sep
参数是指定哑变量之间的分隔符。来看看效果吧。
user_info.city.str.get_dummies(sep=" ")
这样,它提取出了 Bei, Guang, Hai, Jing, Shang, Shen, Zhen, Zhou 这些哑变量,并对每个变量下使用 0 或 1 来表达。实际上与 One-Hot(狂热编码)是一回事。
⭐️方法摘要
这里列出了一些常用的方法摘要。
方法 | 描述 |
---|---|
cat() | 连接字符串 |
split() | 在分隔符上分割字符串 |
rsplit() | 从字符串末尾开始分隔字符串 |
get() | 索引到每个元素(检索第i个元素) |
join() | 使用分隔符在系列的每个元素中加入字符串 |
get_dummies() | 在分隔符上分割字符串,返回虚拟变量的DataFrame |
contains() | 如果每个字符串都包含pattern / regex,则返回布尔数组 |
replace() | 用其他字符串替换pattern / regex的出现 |
repeat() | 重复值(s.str.repeat(3)等同于x * 3 t2 >) |
pad() | 将空格添加到字符串的左侧,右侧或两侧 |
center() | 相当于str.center |
ljust() | 相当于str.ljust |
rjust() | 相当于str.rjust |
zfill() | 等同于str.zfill |
wrap() | 将长长的字符串拆分为长度小于给定宽度的行 |
slice() | 切分Series中的每个字符串 |
slice_replace() | 用传递的值替换每个字符串中的切片 |
count() | 计数模式的发生 |
startswith() | 相当于每个元素的str.startswith(pat) |
endswith() | 相当于每个元素的str.endswith(pat) |
findall() | 计算每个字符串的所有模式/正则表达式的列表 |
match() | 在每个元素上调用re.match,返回匹配的组作为列表 |
extract() | 在每个元素上调用re.search,为每个元素返回一行DataFrame,为每个正则表达式捕获组返回一列 |
extractall() | 在每个元素上调用re.findall,为每个匹配返回一行DataFrame,为每个正则表达式捕获组返回一列 |
len() | 计算字符串长度 |
strip() | 相当于str.strip |
rstrip() | 相当于str.rstrip |
lstrip() | 相当于str.lstrip |
partition() | 等同于str.partition |
rpartition() | 等同于str.rpartition |
lower() | 相当于str.lower |
upper() | 相当于str.upper |
find() | 相当于str.find |
rfind() | 相当于str.rfind |
index() | 相当于str.index |
rindex() | 相当于str.rindex |
capitalize() | 相当于str.capitalize |
swapcase() | 相当于str.swapcase |
normalize() | 返回Unicode标准格式。相当于unicodedata.normalize |
translate() | 等同于str.translate |
isalnum() | 等同于str.isalnum |
isalpha() | 等同于str.isalpha |
isdigit() | 相当于str.isdigit |
isspace() | 等同于str.isspace |
islower() | 相当于str.islower |
isupper() | 相当于str.isupper |
istitle() | 相当于str.istitle |
isnumeric() | 相当于str.isnumeric |
isdecimal() | 相当于str.isdecimal |
函数应用
虽说 Pandas 为我们提供了非常丰富的函数,有时候我们可能需要自己定制一些函数,并将它应用到 DataFrame 或 Series。常用到的函数有:map
、apply
、applymap
。
map
是 Series 中特有的方法,通过它可以对 Series 中的每个元素实现转换。
如果我想通过年龄判断用户是否属于中年人(30岁以上为中年),通过 map
可以轻松搞定它。
# 接收一个 lambda 函数
user_info.age.map(lambda x: "yes" if x >= 30 else "no")
又比如,我想要通过城市来判断是南方还是北方,我可以这样操作
user_info.city
city_map = {
"BeiJing": "north",
"ShangHai": "south",
"GuangZhou": "south",
"ShenZhen": "south"
}
# 传入一个 map
user_info.city.str.replace(" ","").map(city_map)
apply 方法既支持 Series,也支持 DataFrame,在对 Series 操作时会作用到每个值上,在对 DataFrame 操作时会作用到所有行或所有列(通过 axis 参数控制)。
# 对 Series 来说,apply 方法 与 map 方法区别不大。
user_info.age.apply(lambda x: "yes" if x >= 30 else "no")
applymap 方法针对于 DataFrame,它作用于 DataFrame 中的每个元素,它对 DataFrame 的效果类似于 apply 对 Series 的效果。
大家如果感觉可以的话,可以去做一些小练习~~
【练习一】 现有一份关于字符串的数据集,请解决以下问题:
(a)现对字符串编码存储人员信息(在编号后添加ID列),使用如下格式:“×××(名字):×国人,性别×,生于×年×月×日”
(b)将(a)中的人员生日信息部分修改为用中文表示(如一九七四年十月二十三日),其余返回格式不变。
(c)将(b)中的ID列结果拆分为原列表相应的5列,并使用equals检验是否一致。