Pandas
本文主要汇总了读写CSV、Excel表,数据表的合并、拼接和分组聚合操作,数据值的缺失、数据的离散化、数据的抽样与重采样、数据的排序及去重和数据的聚合分析处理以及其他较常用函数的使用方法;并结合相关案例做了练习。但 Pandas 依旧有很多没有学到位,仍需要不断练习,进行项目实战才行。
文章目录
1. 数据结构
1.1 Series
Series 是由一组标签(keys)和数据值(values)构成的一维数据结构,类似大小固定的dict,通过 index 索引标签来访问或修改元素值。其可以保存任何数据类型,比如字符串、浮点数、Python对象等,其标签默认为从 0 开始依次递增的整数,亦可为其他类型。
1.1.1 创建Series
Pandas 使用 Series() 函数来创建 Series 对象,通过这个对象来调用相应的属性和方法。其语法结构如下:
pd.Series(data, index, dtype, name, copy)
参数 | 参数说明 |
---|---|
data | 输入的数据,可以是列表、常量、ndarray 数组等 |
index | 索引值必须唯一,若没有传递索引,则默认为 np.arrange(n) |
dtype | 表示数据类型,若没有提供,则会自动判断得出,默认为 float64 |
name | 设置名称(该列数据的属性名称) |
copy | 表示对 data 进行拷贝,默认为 False |
# 隐式索引
data1 = np.arange(1, 11)
s1 = pd.Series(data1)
# 显示索引
data2 = np.array(['a', 'b', 'c', 'd'])
s2 = pd.Series(data2, index=[100, 101, 102, 103])
# 标量创建,必须提供索引
data3 = 5
s3 = pd.Series(data3, index=np.arange(10))
# dict创建
data4 = {'a': 1, 'b': 2, 'c': 3}
s4 = pd.Series(data4, index=['a', 'b', 'c'])
s5 = pd.Series(data4, index=['c', 'b', 'd', 'a'])
print(s4, "\n", s5)
"""
a 1
b 2
c 3
dtype: int64
c 3.0
b 2.0
d NaN
a 1.0
dtype: float64
"""
注意:传入的数据是字典时,若没传入索引则会按字典的键来构造索引;当传入了索引则会将索引标签与字典中的值进行对应。当传入的索引值无法找到与其对应的值时,使用 NaN 填充。
1.1.2 索引操作
隐式索引
- s[0] 取某一行,也可说取某个元素
- s[[0,1]] 取多行,里面是列表,可存储多个
- s[0:2] 切片操作,取 0-2 行,但只能取到 0和1行,左闭右开原则
- s.iloc[0:2] 专门对隐式索引进行相关操作,左闭右开原则
- s.iloc[[0,1]] 跟 s[[0,1]] 一样
显示索引
- ser[“a”] 取某行
- ser[[“a”,“c”]] 取多行,可以是连续的,也可以是不连续的
- ser[“a”:“d”] 切片,取 a行 到 d行,这里的 d行 是可以取到的,头尾都包含
- ser.loc[“a”:“d”] 专门对显式索引进行相关操作,这里的 d行 也可取到
- ser.loc[[“a”,“c”]] 取 a行、c行 数据
1.1.3 常用属性与方法
名称 | 属性&方法说明 |
---|---|
axes | 以列表的形式返回所有行索引标签 |
dtype | 返回数据对象的数据类型 |
empty | 返回一个布尔值,以判断数据对象是否为空 |
ndim | 返回输入数据的维数(始终为 1) |
size | 返回输入数据的元素数量 |
values | 以 ndarray 形式返回对象中的数据值 |
index | 返回一个RangeIndex对象,用来描述索引的取值范围 |
head() | 返回前 n 行数据(默认n=5)—ser.head() |
tail() | 返回后 n 行数据(默认n=5)—ser.tail() |
isnull() | 检测 Series 中的缺失值,若有缺失,返回 True |
unique() | 删除原数据中重复值,但不修改原数据,返回一维数组 |
axes
s1 = pd.Series(np.arange(1,9))
print(s1.axes)
s2 = pd.Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e'])
print(s2.axes)
"""
[RangeIndex(start=0, stop=8, step=1)]
[Index(['a', 'b', 'c', 'd', 'e'], dtype='object')]
"""
values
s2 = pd.Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e'])
print(s2.values) # [1 2 3 4 5]
index
s2 = pd.Series([1, 2, 3, 4, 5], index=['a', 'b', 'c', 'd', 'e'])
print(s2.index) # Index(['a', 'b', 'c', 'd', 'e'], dtype='object')
isnull()与nonull
- isnull():若值不存在或缺失,则返回 True。
- notnull():若值不存在或缺失,则返回 False。
s1 = [1, 5, np.nan, None, " ", 8, ""]
print(pd.isnull(s1))
print(pd.notnull(s1))
"""
[False False True True False False False]
[ True True False False True True True]
"""
unique()
dic = {"A": 1, "B": 2, "C": 3, "D": 2}
s2 = pd.Series(dic)
s3 = pd.unique(s2)
print(s3, "\n", s2)
"""
[1 2 3]
A 1
B 2
C 3
D 2
"""
Series 相加
Series 相加,会根据索引进行操作,索引相同则数值相加,索引不同则返回 NaN。
1.2 DataFrame
DataFrame:表格型二维数据结构,既有行标签(index),又有列标签(columns),也称异构数据表,即表格中每列的数据类型可不同。DataFrame 简单理解就是 Excel 表数据对象。
1.2.1 创建 DataFrame
Pandas 使用 DataFrame() 函数来创建 DataFrame 对象,通过这个对象来调用相应的属性和方法。其语法结构如下:
pd.DataFrame(data, index, columns, dtype, copy)
参数 | 参数说明 |
---|---|
data | 输入的数据,可以是 ndarray,series,list,dict,标量以及一个 DataFrame |
index | 行标签,若没传递 index 值,则默认行标签是 np.arange(n),n为data元素个数 |
columns | 列标签,若没传递 columns 值,则默认列标签是 np.arange(n) |
dtype | 表示每一列的数据类型 |
copy | 表示复制数据 data,默认为 False |
# 1. 创建空的 DataFrame 对象
df1 = pd.DataFrame()
print("1. 创建空的 DataFrame 对象:\n", df1)
# 2. 列表创建 DataFame 对象
# 2.1 单一列表创建
data2 = [1, 2, 3, 4, 5]
df2 = pd.DataFrame(data, columns=["rank"])
print("2.1 单一列表创建:\n", df2)
# 2.2 嵌套列表创建:内层列表即为一行数据记录
data3 = [['张三', 18], ['Bob', 19], ['Tom', 20]]
df3 = pd.DataFrame(data3, columns=['Name', 'Age'])
print("2.2 嵌套列表创建:\n", df3)
# 3. 字典嵌套列表创建:key---属性, value---属性值(列表长度需一致)
data4 = {'Name': ['Tom', 'Jack', 'Bob', 'Pick'], 'Age': [28, 34, 29, 42]}
df4 = pd.DataFrame(data4, index=['rank1', 'rank2', 'rank3', 'rank4'])
print("3. 字典嵌套列表创建:\n", df4)
# 4. 列表嵌套字典创建 DataFrame 对象:内层字典即为一行数据记录,字典中的 key---属性,value---该行该属性的值
data5 = [{'a': 1, 'b': 2}, {'a': 5, 'b': 10, 'c': 20}]
# 若嵌套的某个字典的 key 缺失,其 value 使用 NaN 填充
df5 = pd.DataFrame(data5, index=['first', 'second'])
print("4. 列表嵌套字典创建:\n", df5)
# 若列索引在字典中没有,则用NAN填充
df6 = pd.DataFrame(data5, index=['first', 'second'], columns=['a', 'b1'])
print("4. 列表嵌套字典创建:\n", df6)
# 5. Series创建DataFrame对象
data6 = {'one': pd.Series([1, 2, 3], index=['a', 'b', 'c']),
'two': pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd'])}
df7 = pd.DataFrame(data6)
print("5. Series 创建 DataFrame对象:\n", df7)
部分输出结果如下:
4. 列表嵌套字典创建:
a b c
first 1 2 NaN
second 5 10 20.0
4. 列表嵌套字典创建:
a b1
first 1 NaN
second 5 NaN
5. Series 创建 DataFrame对象:
one two
a 1.0 1
b 2.0 2
c 3.0 3
d NaN 4
注意:Series 创建 DataFrame 对象时,其输出结果的行索引是所有 Series 的 index 的合集。
1.2.2 索引操作
列索引操作
DataFrame 可以使用列索引来完成数据的选取、添加和删除操作。
-
列索引选取数据列:
data = {'one': pd.Series([1, 2, 3], index=['a', 'b', 'c']), 'two': pd.Series([1, 2, 3, 4], index=['a', 'b', 'c', 'd']), 'three': pd.Series([5, 6, 7, 8], index=['a', 'b', 'c', 'd']), 'four': pd.Series([5, 2, 8, 4], index=['a', 'b', 'd', 'e']), 'five': pd.Series([5, 2, 7, 4], index=['a', 'd', 'e', 'f'])} df = pd.DataFrame(data) # 选择 'one','three','five' 三列 df[['one','three','five']] df['one':'two'] # 无效---使用 loc 函数
-
列索引添加数据列:
# 使用 df['列']=值,插入新的数据列 df['six'] = pd.Series([10, 20, 30], index=['a', 'b', 'c']) # 用已经存在的数据列做相加运算:NaN+...=NaN df['seven'] = df['one'] + df['four'] # 使用 insert() 方法插入新的列 # 数值 7 代表插入到 columns 列表的索引位置 df.insert(7, column='score', value=[91, 90, 75, 89, 87, 92])
-
列索引删除数据列:
# 使用 del 删除 del df['one'] # 使用 pop() 删除 df.pop('score')
行索引操作
-
标签索引选取:
# loc函数:接收两个参数(行和列),参数间用“逗号”隔开;该函数只能接收标签索引 df.loc['a'] df.loc['a':'c','five'] df.loc['a':'d', 'two':'five'] # 左闭右闭 df.loc[['a', 'c', 'e'], ['two', 'five']]
-
整数索引选取:
# iloc函数:接收两个参数(行和列),参数间用“逗号”隔开;该函数只能接收整数索引 df.iloc[2] df.iloc[2,1] # 取第3行第2列数据 df.iloc[1:4, 1:3] # 左闭右开 df.iloc[[0, 2, 4], [1, 3]]
-
切片索引选取:
# 选取多行:左闭右开 df[2:4]
-
添加数据行:
df = pd.DataFrame([[1, 2], [3, 4]], columns=['a', 'b']) df2 = pd.DataFrame([[5, 6], [7, 8]], columns=['a', 'b']) # 在行末追加新数据行 df = df.append(df2) # Use pandas.concat """ a b 0 1 2 1 3 4 0 5 6 1 7 8 """
-
删除数据行:
df.drop(labels, axis=0, index, columns, level, inplace=False, errors='raise')
参数 参数说明 labels int or str,结合 axis,表示带 label 标识的行或者列;如 (labels=‘A’, axis=1) 表示A列 axis 0 表示按行删,1 表示按列删 index 原表行索引 columns 原表列名 inplace bool,默认False,表示删除某行后不修改原表 # 删除A列中包含数值4 所在的行 df.drop(index=data[data['A'].isin([4])].index[0]) df.drop(index=data[data['A']==4].index[0]) # 删除 columns 为 'A' 的列 df.drop(columns='A') df.drop(labels='A', axis=1) # 删除index=0的行 df.drop(index=0)
1.2.3 常用属性与方法
名称 | 属性&方法说明 |
---|---|
T | 行和列转置 |
axes | 返回一个仅以行轴标签和列轴标签为成员的列表 |
dtypes | 返回每列数据的数据类型 |
empty | DataFrame中没有数据或者任意坐标轴的长度为0,则返回True |
ndim | 轴的数量,也指数组的维数----2 |
shape | 返回一个元组,表示了 DataFrame 维度 |
size | DataFrame中的元素数量 |
values | 使用 numpy 数组表示 DataFrame 中的元素值 |
head() | 返回前 n 行数据 |
tail() | 返回后 n 行数据 |
shift() | 将行或列移动指定的步幅长度 |
shift() 移动行或列
df.shift(periods=1, freq=None, axis=0)
参数 | 参数说明 |
---|---|
peroids | int,表示移动的幅度,可以是正数,也可以是负数,默认值为1 |
freq | 日期偏移量,默认值为None,适用于时间序;取值为符合时间规则的字符串 |
axis | 0 (默认)或 “index” 表示上下移动;1 或 “columns” 表示左右移动 |
fill_value | 该参数用来填充缺失值 |
return | 移动后的 DataFrame 副本 |
info = pd.DataFrame({'a_data': [40, np.nan, 39, 32, 18],
'b_data': [20, 37, np.nan, 35, 45],
'c_data': [22, 17, 11, 25, np.nan]})
# 移动幅度为-3:从下往上移三行,空出来的三行填充NaN
info1 = info.shift(periods=-3) # 深拷贝
print(info1)
# 将因移动而导致缺失的值替换为 500
info2 = info.shift(periods=2, fill_value=500, axis=1)
print(info2)
从下往上移三行: 从左往右移两列:
a_data b_data c_data a_data b_data c_data
0 32.0 35.0 25.0 0 500.0 500.0 40.0
1 18.0 45.0 NaN 1 500.0 500.0 NaN
2 NaN NaN NaN 2 500.0 500.0 39.0
3 NaN NaN NaN 3 500.0 500.0 32.0
4 NaN NaN NaN 4 500.0 500.0 18.0
2. 文件操作
2.1 读写CSV
pd.read_csv()
该函数用于读取文本文件。其语法格式如下:
# 常用参数
pd.read_csv(filepath_or_buffer, sep, delimiter, delim_whitespace, header, names, index_col, usecols, mangle_dupe_cols, prefix, dtype, skiprows, nrows, na_values, parse_dates, date_parser, infer_datetime_format, iterator)
-
filepath_or_buffer:数据输入的路径;可是文件路径、url,实现read方法的任意对象。
pd.read_csv("temp.csv") pd.read_csv("http://localhost/temp.csv") f = open("temp.csv", encoding="utf-8") pd.read_csv(f)
-
sep:读文件时指定的分隔符(一定要与"csv文件的分隔符"一致),默认为逗号。
-
delimiter:分隔符的另一名字,功能同 sep。
-
delim_whitespace :默认为 False,为 True 时,表示分割符为空白字符(空格、“\t” 等)。
-
header:设置列名称,默认为 “infer”,与 names 参数有关。
-
names:当 names 没被赋值时,header为0;当 names 被赋值,header 没被赋值时,header 为 None。若都被赋值,则先执行 header 确定表头,再执行 names 替换表头。
# temp.csv:第一行为表头(id,name,address,date,age,result) # names 没有被赋值, header 也没赋值: 第一行为表头 df = pd.read_csv('./datas/temp.csv', sep=',', dtype={"id": str}) # names 没有被赋值, header 被赋值: 第二行为表头了,原先第一行没有了 df = pd.read_csv('./datas/temp.csv', sep=',', dtype={"001": str}, header=1) # names 被赋值, header 没有被赋值: 原先第一行(id,name,address,date,age,result)变成一条数据记录了 df = pd.read_csv('./datas/temp.csv', sep=',', dtype={"编号": str}, names=["编号", "姓名", "地址", "日期", "年龄", "结果"]) # names和header都被赋值: 用 names 值覆盖原先第一行表头(id,name,address,date,age,result) df = pd.read_csv('./datas/temp.csv', sep=',', dtype={"编号": str}, header=0, names=["编号", "姓名", "地址", "日期", "年龄", "结果"])
-
index_col:读取时指定某列为索引,如:index_col=“id”;默认索引为 np.arange(n)。
-
usecols:读取数据表中真正要使用到的列,如:usecols=[“name”, “address”]。
-
mangle_dupe_cols:当导入的数据含有同名的列时,会在重名的列名后加 .1;该参数默认为 True,若为 False,会抛出异常。
-
prefix:当导入的数据没有 header 时,会自动在列名前加一个前缀,如:prefix=“Pick”。
-
dtype:设定属性的数据类型,如:dtype={“id”: str, “age”: float}。
-
skiprows:过滤行,可传递列表或函数。注意:先过滤,后确定表头。
# 过滤索引大于0且%2等于0的记录 df = pd.read_csv('temp.csv', sep=",", skiprows=lambda x: x > 0 and x % 2 == 0) # 过滤第一行,第一行若是表头,过滤之后第二行就变成表头了 df = pd.read_csv('temp.csv', sep=",", skiprows=[0])
-
nrows:一次性读入的文件行数。
-
na_values:配置哪些值需要处理成 NaN,传递字典实现只对指定列进行替换。
# 将属性 name,result 指定的值替换为 NaN df = pd.read_csv('temp.csv', sep=",", na_values={"name": ["Jack", "Pick"], "result": ["对"]})
-
parse_dates:指定某些列为时间类型,一般搭配 date_parser 使用。
-
date_parser:用来配合parse_dates参数,因为有的列虽是日期,但没办法直接转化,需要指定一个解析格式:
from datetime import datetime df = pd.read_csv('./datas/temp.csv', sep=",", parse_dates=["date"], date_parser=lambda x: datetime.strptime(x, "%Y-%m-%d")) df.loc[:,"date"] """ 0 2023-02-24 1 2021-06-14 2 2018-08-05 Name: date, dtype: datetime64[ns] """
-
infer_datetime_format:默认为 False。若为 True 且 parse_dates 可用,将尝试转换为日期类型,若可转换,转换方法并解析,在某些情况下会快 5~10 倍。
df = pd.read_csv('./datas/temp.csv', sep=",", parse_dates=["date"], infer_datetime_format=True) df.loc[:, "date"] """ 0 2023-02-24 1 2021-06-14 2 2018-08-05 Name: date, dtype: datetime64[ns] """
-
iterator:bool类型,默认为False。若为True,则返回一个 TextFileReader 对象,以便逐块处理文件。在文件很大、内存无法容纳所有数据文件时,可以分批读入,依次处理。
chunk = pd.read_csv('girl.csv', sep="\t", iterator=True) # 不够指定行数,有多少返回多少 print(chunk.get_chunk(50)) # 但在读取完毕之后,再读就会报错了 chunk.get_chunk(5)
df.to_csv()
该函数用于将 DataFrame 数据保存到 CSV 文本中。其语法格式如下:
# 常用参数
df.to_csv(path_or_buf, sep, na_rep, float_format, columns, header, index, index_label, mode, encoding, compression, chunksize, date_format)
-
path_or_buf:文件路径,若没指定则直接返回数据的 CSV.str。
data = {'ID': ["001", "002"], 'Name': ['Pick', pd.NaT], 'Language': ['Python', 'C++'], 'Score': [89.0, 92.5]} df = pd.DataFrame(data) # 返回数据的 CSV.str csv_data = df.to_csv() # 若有文件路径,csv_data=None
-
sep : 输出文件中字段的分隔符,默认为 “,”。
-
na_rep : 缺失数据填充,默认为“”。
-
float_format : 小数点保留位数。
# 浮点数保留两位小数 df.to_csv('./datas/data1.csv', sep="\t", na_rep='Tom', float_format='%.2f')
-
columns : 原数据表中哪些列需要保存到文件中,如:columns=[‘Name’,‘Score’],则仅提取这两列数据保存到文件中。
-
header : 是否保存列名,默认为 True ,即传入与 columns 长度一致的字符串列表,给对应位置上的列名起个别名;若为 False,则不保存列名,会丢失表头。
# header=False会丢失表头 df.to_csv('./datas/data1.csv', sep="\t", na_rep='Tom', columns=['Name', 'Language', 'Score'], header=False) # 给要保存的原数据列名起个别名 df.to_csv('./datas/data1.csv', sep="\t", na_rep='Tom', columns=['Name', 'Language', 'Score'], header=['姓名', '语言', '成绩'])
-
index : 是否保存行索引,默认为 True。
# index=False:不会生成默认行索引0,1,2.... df.to_csv('./datas/data1.csv', sep="\t", na_rep='Bon', index=False)
-
index_label : index=True 时生成的默认行索引的列标签名。
# index=True:会生成默认行索引0,1,2....;index_label:即给该行索引所在列起个列名 df.to_csv('./datas/data1.csv', sep="\t", na_rep='Bon', index_label="编号")
-
mode:文件的写入模式,默认为’w’。
- r : 只能读,必须存在,可在任意位置读取
- w : 只能写,可以不存在,每次均从头写
- a : 只能写,可以不存在,只能在结尾追加写
- r+ : 可读可写,必须存在,可在任意位置读写,读写共用同一指针
- w+ : 可读可写,可以不存在,每次均从头写
-
encoding:输出文件中字符串编码格式,默认为 utf-8;对于BOM文件应为 utf_8_sig。
-
compression:若为字符串,表示压缩模式;若为dict,则 method 键对应的值是压缩模式。压缩模式有:{‘zip’, ‘gzip’, ‘bz2’, ‘zstd’, ‘tar’}。
# Create ‘out.zip’ containing ‘out.csv’ compression_opts = dict(method='zip', archive_name='out.csv') df.to_csv('out.zip', index=False, compression=compression_opts)
-
chunksize:大文件分块写入,一次写入行数。
-
date_format:日期时间对象的格式字符串,如:date_format=‘%Y-%m-%d %H:%M:%S’。
2.2 读写Excel
pd.read_excel()
该函数用于读取 Excel 文件。其语法格式如下:
# 常用参数
pd.read_excel(io, sheet_name, header, names, index_col, usecols, squeeze, dtype, engine, converters, true_values, false_values, skiprows, nrows, na_values, keep_default_na, na_filter='True', parse_dates, date_parser, thousands, comment, skipfooter=0, verbose)
参数 | 参数说明 |
---|---|
io | Excel 文件的存储路径,可为URL |
sheet_name | [str,int,list]:指定读取的工作表名称;若为 None 则返回 所有Sheet表 |
header | [int,list of int]:指定作为列名的行,默认0,即取第一行的值为列名;若数据不包含列名,则设定 header = None;若设置 header=2,则表示将前两行作为多重索引(Multiindex) |
names | 适用于 Excel 缺少列名,或需要重新定义列名的情况;names 的长度必须等于 Excel 表格列的长度,否则会报错 |
index_col | [int,list of int]:用做行索引的列,工作表的列名称,如 index_col = ‘列名’,也可是整数或列表;若使用了 usecols 作为数据的子集,则该参数是基于该子集;默认为 Node:若原数据表均是纯数据,没有索引列,则读取数据时自动生成默认行索引RangeIndex |
usecols | [int,list],默认为None:有选择的读取某些列 |
squeeze | bool,默认为False,若为True则解析的数据只包含一列时,返回一个Series |
converters | [dict,default None]:规定每一列的数据类型,字典值是接受一个输入参数(Excel单元格内容)并返回转换后的内容的函数 |
skiprows | 接收受一个列表,表示跳过指定行数的数据,从头部第一行开始 |
nrows | 需要读取的行数 |
skipfooter | 接收一个列表,省略指定行数的数据,从尾部最后一行开始 |
parse_dates | 指定某些列为时间类型,一般搭配 date_parser 使用;[[1,3]]->合并列1和3并解析为单个日期列;{‘foo’ : [1, 3]} -> 解析列1,3为日期并调用结果 foo |
date_parser | 用于将字符串列序列转换为datetime实例数组;默认情况下使用dateutil.parser.parser执行转换 |
na_values | str,dict等,默认None:识别为NA/NaN的附加字符串,若传递了dict,则指定每列的NA值 |
keep_default_na | bool,默认为 True:解析数据时是否包含默认的 NaN 值;为True 且 na_values 给了值,则用其值替换 |
verbose | bool,默认为 False:指示放置在非数字列中的NaN值的数量 |
engine | str, default None:可接受的参数有“ xlrd”,“ openpyxl”或“ odf”,用于使用第三方库去解析 excel 文件 |
# 返回 表1:df[0] 与 表二:df[1]
df = pd.read_excel('./datas/data2.xlsx', sheet_name=[0, 1])
# 返回 表1:df[0]与表二:df[1]---下表索引列表
df = pd.read_excel('./datas/data2.xlsx', sheet_name=np.arange(2).tolist())
import openpyxl
# 打开文件
work_book = openpyxl.load_workbook('./datas/data2.xlsx')
# 获取工作簿所有sheet表对象名称
sheets_name = work_book.get_sheet_names()
df = pd.read_excel('./datas/data2.xlsx', sheet_name=sheets_name)
# 第2列的所有名称加上"",第三列的所有数值都减10
df = pd.read_excel('./datas/data2.xlsx', converters={1: lambda x: "\"" + x + "\"", 2: lambda x: float(x) - 10})
df.to_excel()
该函数用于将 DataFrame 数据保存到 Excel 表中。其语法格式如下:
# 常用参数
pd.to_excel(excel_writer, sheet_name, na_rep, float_format, columns, header='True', index, index_label, startrow, startcol, engine, merge_cells='True', encoding, inf_rep, verbose, freeze_panes)
参数 | 参数说明 |
---|---|
excel_writer | str 或 ExcelWriter;文件路径或现有 ExcelWriter |
sheet_name | str,默认为 Sheet1;包含DataFrame的工作表名称 |
na_rep | str,默认为 ‘’;缺失的数据表示 |
columns | sequence 或 str 的 list,可选的;要写入的列名 |
header | bool 或 str 的 list;表头,若给了字符串列表,则 header 为其别名 |
index | bool,默认为 True;指定行名 |
index_label | str 或 sequence;index=True时生成的默认行索引的列标签名 |
startrow | int;默认为0;新表左上角单元格从第几行开始转储 DataFrame |
startcol | int;默认为0;新表左上角单元格从第几列开始转储 DataFrame |
engine | str;可选;编写要使用的 engine,openpyxl或xlsxwriter;通过选项设置:io.excel.xlsx.writer |
inf_rep | str,默认为 inf:表示无穷大 |
verbose | bool,默认为True;在错误日志中显示更多信息 |
freeze_panes | int,长度为 2 的元组,可选;指定要冻结的最底部的行和最右边的列 |
np.random.seed(100)
df = pd.DataFrame(np.random.randint(60, 100, (5, 3)), columns=['Python', 'Java', 'C++'],
index=['a', 'b', 'c', 'd', 'e'])
df.loc[['a', 'c'], ['Python', 'C++']] = np.NaN
# 命名工作表名称:"成绩数据",缺失值填充:'0',指定原行索引列名:"编号"
df.to_excel('./datas/data2.xlsx', sheet_name='成绩数据', na_rep='0', index_label='编号')
# 指定写入的列:['Python', 'C++'],并起个别名
df.to_excel('./datas/data2.xlsx', columns=['Python', 'C++'], header=['牛一', '牛二'])
# 新表从第 2 行,第 2 列开始转储 DataFrame 数据
df.to_excel('./datas/data2.xlsx', startrow=1, startcol=1)
借助 ExcelWriter对象 创建多个 Sheet:
df2 = df1.copy()
with pd.ExcelWriter('output.xlsx') as writer:
df1.to_excel(writer, sheet_name='Sheet_name_1')
df2.to_excel(writer, sheet_name='Sheet_name_2')
ExcelWriter 也可用于将数据追加到现有的 Excel 文件中:
with pd.ExcelWriter('output.xlsx', mode='a') as writer:
df.to_excel(writer, sheet_name='Sheet_name_3')
3. 表格操作
3.1 表格合并
pd.merge()
pd.merge() 函数能够进行高效的数据表左右合并操作,与 SQL 关系型数据库的 MERGE 用法非常相似。其语法格式如下:
pd.merge(left, right, how='inner', on, left_on, right_on, left_index, right_index, sort=True, suffixes=('_x', '_y'), copy=True, indicator=False)
df.merge(right, how='inner', on, left_on, right_on, left_index, right_index, sort=True, suffixes=('_x', '_y'), copy=True, indicator=False)
参数 | 参数说明 |
---|---|
left / right | 两个不同的 DataFrame 对象 |
on | 指定用于连接的键(列名),该键必须同时存在于左右两表中;若不指定,相同信息的列都会作为拼接依据;其不能与 left_index&right_index 同用 |
left_on | 指定左侧 DataFrame 中作连接键的列名;该参数在左、右列名不相同,但表达的含义相同时非常有用 |
right_on | 指定右侧 DataFrame 中作连接键的列名 |
left_index | bool,默认为 False;若为 True 则用左侧 DataFrame 的行索引作为连接键,若 DataFrame 具有多层索引(MultiIndex),则层的数量必须与连接键的数量相等 |
right_index | bool,默认为 False;若为 True 则用右侧 DataFrame 的行索引作为连接键 |
how | 要执行的合并类型,从 {‘left’, ‘right’, ‘outer’, ‘inner’} 中取值,默认为“inner”内连接 |
sort | bool,默认为True,对合并后的数据进行排序;若为 False,则按照 how 给定的参数值进行排序 |
suffixes | 字符串组成的元组;当左右 DataFrame 存在相同列名时,通过该参数可以在相同的列名后附加后缀名,默认为(‘_x’,‘_y’) |
copy | 默认为 True,表示对数据进行复制(深拷贝) |
indicator | 默认为 False,不显示数据来源,若为 True,会显示拼接后的表中哪些信息来自于哪一个表格 |
# 加载数据表
df_hh = pd.read_excel('./datas/data2.xlsx', sheet_name=1, index_col='工号', dtype={'工号': str})
# 加载数据表
df_ss = pd.read_excel('./datas/data2.xlsx', sheet_name=3, index_col='工号', dtype={'工号': str})
# 按 工号,员工姓名 拼接表格: 默认取交集
df = pd.merge(df_hh, df_ss, on=['工号','员工姓名'])
# 按 工号 拼接表格: 001 一条记录
df = pd.merge(df_hh, df_ss, on=['工号'])
# 按 员工姓名 拼接表格: '张三','王七' 两条记录
df = pd.merge(df_hh, df_ss, on=['员工姓名'])
# on 无参:表中数据比较除了行索引'工号'所在列之外的相同列的数据元素交集,
# 因为 left_index & right_index 默认为 False:行索引不参与
df = pd.merge(df_hh, df_ss)
# 按 行索引'工号' 拼接表格
df = pd.merge(df_hh, df_ss, left_index=True, right_index=True)
"""
索引有不能对齐的地方,在默认的内连接情况下,只会把索引对齐的记录进行拼接
注:Can only pass argument "on" OR "left_index" and "right_index", not a combination of both.
"""
# 若加载数据不指定 '工号' 为行索引,则会生成默认行索引
df_hh = pd.read_excel('./datas/data2.xlsx', sheet_name=1, dtype={'工号': str})
df_ss = pd.read_excel('./datas/data2.xlsx', sheet_name=3, dtype={'工号': str})
# 此时 on 没有参数, 相同信息的列('工号','员工姓名')都会作为拼接依据(默认取交集)
df = pd.merge(df_hh, df_ss)
# 按 默认行索引 拼接表格(注意员工姓名的不同)
df = pd.merge(df_hh, df_ss, left_index=True, right_index=True)
# 按 默认行索引 拼接表格(注意员工姓名的不同)
df = pd.merge(df_ss, df_hh, left_index=True, right_index=True)
how | 等效 sql | 功能说明 |
---|---|---|
left | left outer join | 使用左表key:保留所有左表的信息,把右表中键与左表一致的信息拼接进来,标签不能对齐的部分,填充NaN |
right | right outer join | 使用右表key:保留所有右表的信息,把左表中键与右表一致的信息拼接进来,标签不能对齐的部分,填充NaN |
outer | full outer join | 使用左右两表 key 的并集:保留两个表的所有信息,拼接时遇到标签不能对齐的部分,填充NaN |
inner | inner join | 使用左右两表 key 的交集:保留 key 的信息完全一致的记录 |
# 按 工号,员工姓名 拼接表格: 要求 右连接
df = pd.merge(df_hh, df_ss, on=['工号', '员工姓名'], how='right')
# 按 工号,员工姓名 拼接表格: 要求 内连接
df = pd.merge(df_hh, df_ss, on=['工号', '员工姓名'], how='inner')
df.join()
df.join() 函数:按照索引进行连接,在实际应用中,常常采用 set_index() 临时设置索引。其语法格式如下:
df.join(other, on=None, how='left', lsuffix='', rsuffix='', sort=False)
参数 | 参数说明 |
---|---|
other | 具有名称字段集的Series,或DataFrame列表;若传递Series,则必须设置其name属性,并将其用作生成的连接DataFrame中的列名 |
on | 列名称,或列名称的list/tuple,或类似形状的数组,连接的列,默认使用索引连接 |
how | {‘left’,‘right’,‘outer’,‘inner’},默认:‘left’,左连接 |
lsuffix | str:左 DataFrame 中重复列的后缀 |
rsuffix | str:右 DataFrame 中重复列的后缀 |
sort | bool,默认为 False;通过联接键按词法对结果 Dataframe 进行排序。如果为False,则连接键的顺序取决于连接类型(关键字) |
# 加载数据表:指定 '工号' 为行索引
df_h = pd.read_excel('./datas/data3.xlsx', sheet_name=0, dtype={'工号': str}, index_col='工号')
df_s = pd.read_excel('./datas/data3.xlsx', sheet_name=1, dtype={'工号': str}, index_col='工号')
# 默认使用行索引进行连接
df = df_h.join(df_s, how='inner', lsuffix='_x', rsuffix='_y')
# 等价于如下 merge:
df = pd.merge(df_h, df_s, on=['工号'], how='inner')
"""
当两表中列名重复时,若不修改,则会报如下错误:
columns overlap but no suffix specified: Index(['员工姓名'], dtype='object')
此时需要使用参数: lsuffix 与 rsuffix
"""
# 重置索引为'员工姓名',以员工姓名为索引进行连接
df = df_h.set_index(['员工姓名']).join(df_s.set_index(['员工姓名']), on=['员工姓名'], how='inner')
# 重新加载数据表:生成默认行索引
# 或在原表基础上使用 reset_index 函数
df_h = pd.read_excel('./datas/data2.xlsx', sheet_name=1, dtype={'工号': str})
df_s = pd.read_excel('./datas/data2.xlsx', sheet_name=3, dtype={'工号': str})
# 以 ['工号', '员工姓名'] 为索引连接表格
df = df_h.set_index(['工号', '员工姓名']).join(df_s.set_index(['工号', '员工姓名']), how='inner')
df.align()
df.align() 函数可使用指定的每个轴索引,将轴上的两个对象对齐。其语法格式如下:
df.align(other, join='outer', axis=None, level, copy=True, fill_value, method, limit, fill_axis=0, broadcast_axis=None)
参数 | 参数说明 |
---|---|
other | Series or DataFrame |
join | {‘outer’, ‘inner’, ‘left’, ‘right’},默认’outer’ |
axis | 另一个对象的 axis 是行对齐还是列对齐,默认 None,对齐 index (0),columns (1),或 both (None) |
fill_value | 默认np.NaN,用于缺失值的值 |
method | {‘backfill’, ‘bfill’, ‘pad’, ‘ffill’, None},默认 None |
limit | int, 默认 None,若指定了method,则这是向前/向后填充的连续NaN值的最大数量,即若存在超过此数量的连续 NaN 的间隙,则仅部分填充;若未指定method,则这是沿整个轴填充NaN的最大条目数 |
fill_axis | {0 or ‘index’, 1 or ‘columns’},默认0 |
broadcast_axis | {0 or ‘index’, 1 or ‘columns’},默认None,对齐两个不同尺寸的对象,需沿此 axis 广播值 |
返回值 | tuple:(left,right) |
df1 = pd.DataFrame(np.ones((5, 2)), columns=['a', 'b'], index=pd.date_range('1/1/2023', periods=5))
df2 = pd.DataFrame(np.ones((6, 3)) * 2, columns=['a', 'b', 'c'], index=pd.date_range('1/3/2023', periods=6))
# 向前填充,注意设定fill_value值时, method不起作用
df1.align(df2, method='pad', limit=1)
df1.align(df2, method='pad', limit=1)[0]
当 axis=0 时,outer 只取行索引的并集,列索引并没有取并集(按行对齐):
df1.align(df2, axis=0)
输出结果如下:
( a b
2023-01-01 1.0 1.0
2023-01-02 1.0 1.0
2023-01-03 1.0 1.0
2023-01-04 1.0 1.0
2023-01-05 1.0 1.0
2023-01-06 NaN NaN
2023-01-07 NaN NaN
2023-01-08 NaN NaN,
a b c
2023-01-01 NaN NaN NaN
2023-01-02 NaN NaN NaN
2023-01-03 2.0 2.0 2.0
2023-01-04 2.0 2.0 2.0
2023-01-05 2.0 2.0 2.0
2023-01-06 2.0 2.0 2.0
2023-01-07 2.0 2.0 2.0
2023-01-08 2.0 2.0 2.0)
axis=1 时,outer 也只取列索引的并集,行索引并没有取并集(按列对齐)。
3.2 表格连接
pd.concat()
pd.concat() 函数可沿指定的轴将多个 DataFrame 或 Series 拼接到一起。其语法格式如下:
pd.concat(objs, axis=0, join='outer', ignore_index=False, keys=None, levels=None, names=None, verify_integrity=False, sort=None, copy=True)
参数 | 参数说明 |
---|---|
objs | 一个序列或 Series、DataFrame 对象构成的 list |
axis | 默认 axis=0:列对齐,沿垂直方向合并;axis=0:行对齐,沿水平方向合并 |
join | 指定连接方式,可为{“inner”,“outer”},默认为 outer:表示取并集 |
ignore_index | bool,默认为 False,若为 True,表示不在连接的轴上使用索引 |
keys | 相接的时候再加上一个层次的 key 来识别数据源自于哪张表,常搭配 names 使用 |
names | 给拼接后形成的数据结构添加名字,如:names=[‘来源表’, ‘行索引’] |
levels | 序列列表,默认值无:用于构建MultiIndex的特定级别(唯一值),否则,它们将从键推断 |
# 加载数据表:生成默认行索引 RangeIndex
df_h = pd.read_excel('./datas/data3.xlsx', sheet_name=0, dtype={'工号': str})
df_s = pd.read_excel('./datas/data3.xlsx', sheet_name=1, dtype={'工号': str})
# 沿水平方向合并,并标出新表各数据具体来自于哪张表:默认行索引,outer
df = pd.concat([df_h, df_s], axis=1, keys=['df_h', 'df_s'], names=['来源表', '行索引'])
# 默认行索引 左右拼接: inner(行索引相同,元素值直接拼接在一行上)
df = pd.concat([df_h, df_s], axis=1, keys=['df_h', 'df_s'], names=['来源表', '行索引'], join='inner')
# 沿垂直方向合并,并标出新表各数据具体来自于哪张表:默认行索引,outer
df = pd.concat([df_h, df_s], keys=['df_h', 'df_s'], names=['来源表', '行索引'])
# 默认行索引 上下拼接: inner(列索引相同,元素值直接拼接在一列上)
df = pd.concat([df_h, df_s], keys=['df_h', 'df_s'], names=['来源表', '行索引'], join='inner')
# 加载数据表:指定 '工号' 为行索引
df_h = pd.read_excel('./datas/data3.xlsx', sheet_name=0, dtype={'工号': str}, index_col='工号')
df_s = pd.read_excel('./datas/data3.xlsx', sheet_name=1, dtype={'工号': str}, index_col='工号')
# 沿水平方向合并,并标出新表各数据具体来自于哪张表
df = pd.concat([df_h, df_s], axis=1, keys=['df_h', 'df_s'], names=['来源表', '行索引'])
# 左右拼接: inner(行索引相同,相同列名元素值相同的记录拼接在一行上)
df = pd.concat([df_h, df_s], axis=1, keys=['df_h', 'df_s'], names=['来源表', '行索引'], join='inner')
# 等效于:
df = pd.merge(df_h, df_s, on=['工号', '员工姓名'])
# 沿垂直方向合并,并标出新表各数据具体来自于哪张表
df = pd.concat([df_h, df_s], keys=['df_h', 'df_s'], names=['来源表', '行索引'])
# 上下拼接: inner(列名相同,元素值无NaN的拼接在一列上)
df = pd.concat([df_h, df_s], keys=['df_h', 'df_s'], names=['来源表', '行索引'], join='inner')
3.3 表格分组
df.groupby()
分组,即将数据表中所有行按照一列或多列来划分,分为多个组,列值相同的在同一组,列值不同的在不同组。Pandas 分组操作需使用 df.groupby() 函数,其与 SQL 的 group by 功能非常相似。分组后,就得到一个 groupby 对象(DataFrameGroupBy),代表已经被分开的各个组(子DataFrame),后续所有的动作,如计数,求平均值等,都是针对这个对象操作。其语法格式如下:
df.groupby(by, axis=0, level, as_index, sort, group_keys, squeeze, observed, dropna=True)
参数 | 参数说明 |
---|---|
by | 标签或标签列表或函数;依据 by 分组;若 by 是一个函数,它会在对象索引的每个值上调用 |
axis | {0或**‘index’,1或’columns’},沿rows (0)或columns (1)**分组拆分 |
level | int,若 axis 是 MultiIndex,则按一个或多个特定级别分组 |
as_index | bool,默认为True;对于聚合输出,返回带有组标签的对象作为索引;若为False,则是有效的 sql 风格的分组输出 |
sort | bool,默认为True;排序组键:不影响每组中观察顺序,且保留每组中行的顺序;关闭此功能可获得更好的性能 |
group_keys | bool,默认为True;调用 apply 时,将组键添加到 index 以识别片段 |
squeeze | bool,默认为False;若可能,减小返回类型的维数,否则返回一致的类型 |
observed | bool,默认为False;仅当任何 groupers 为分类者时才适用;若为True:仅显示分类 groupers 的观测值;若为 False:显示分类所有值 |
dropna | bool,默认为True:组键包含 NA值时,则 NA 值连同行/列将被删除;若为 False,则 NA值也将被视为组中的键 |
df.groupby() 函数返回的对象是一系列键值对,其中键是分组的字段值,值是该字段值下的数据表。可用循环对分组后的结果进行遍历。
# 按多列分组,并遍历分组结果:
group = df.groupby([col1, col2]) # 先按'col1'列的值分组;每组内,再按'col2'列分组
for key, value in group:
print(key, " ", value)
# 查看每组的统计数据:
df.groupby(col1).describe()
# 查看指定列的统计信息
df.groupby(col1).describe()[col2]
# 查看纵向视图
df.groupby(col1).describe().unstack()
# 组内数值列求和:每组内,只有数值列能求和,非数值列不可求和
df.groupby(col).sum()
# 组内成员数: 每组内,按列统计每组的成员数
df.groupby(col).count()
# 组内数值列标准差:每组内,统计所有数值列的标准差,非数值列无标准差
df.groupby(col).std()
# 统计单个数值列的标准差
df.groupby(col1)[col2].std()
# 组内数值列二分位数:每组内,统计所有数值列的二分位数,非数值列无二分位数
df.groupby(col).quantile()
# 组内应用函数:每组内,可指定只求某一列的统计指标,包括平均数,方差等
df.groupby(col1)[col2].apply(np.mean)
# 组内应用多个函数:agg函数,其参数可为列表,其中可包含各个函数
df.groupby(col1).agg([...])
# 同时查看每组内,某数值列的多个统计指标
group = df.groupby(['col1'])
df1 = group['col2'].agg([np.mean, np.std]) # 组内应用均值,方差两个函数
组内离散列计数:df.groupby(col1)[col2].value_counts()。
数据表中的列按值是否连续,可分为连续值列、离散值列。对离散值列,可统计其不重复值的个数。对连续值列,统计不重复值一般没有意义。统计结果是一个Series对象。
# 组内不同列用不同函数:
df.groupby(col1).agg({col2:func2, col3:func3,...})
group = df.groupby(['col1'])
df1 = group.agg({'col2':np.mean, 'col3':np.std})
具体用法也可参考博客:Pandas教程 | 超好用的Groupby用法详解。
组的转换操作
transform() 函数可在组的行或列上执行转换操作,即会对每一条数据求得相应的结果,同一组内的样本会有相同的值,组内求完相应值后会按照原索引的顺序返回一个与原表大小相同的索引对象。其语法格式如下:
df.transform(func, axis=0, *args, **kwargs)
参数 | 参数说明 |
---|---|
func | function, str, list 或 dict:用于转换数据的函数;如果是函数,则必须在传递DataFrame或传递到DataFrame.apply时工作;值形式:[np.exp. ‘sqrt’] |
axis | {0 or ‘index’, 1 or ‘columns’},默认 0;0 或 ‘index’:应用函数到每一列 |
return | DataFrame:必须具有与输入的 DataFrame 相同的长度(size 一致) |
company = ["A", "B", "C", "D"]
np.random.seed(50)
df = pd.DataFrame({"company": [company[x] for x in np.random.randint(0, len(company), 10)],
"salary": np.random.randint(5, 50, 10), "age": np.random.randint(18, 30, 10)})
# 原数据表表头:company,salary,age;计算出不同公司员工的平均薪资,并新增一列: avg_salary(同公司均薪相同)
# 方式一: map映射
avg_salary_dict = df.groupby('company')['salary'].mean().to_dict()
df['avg_salary'] = df['company'].map(avg_salary_dict)
# 方式二: transform
df['avg_salary'] = df.groupby('company')['salary'].transform('mean')
# 计算出不同公司员工距平均薪资的差距
df['avg_salary_dis'] = df.groupby('company')['salary'].transform(lambda x:x-x.mean())
组的过滤操作
分组后使用 filter() 函数,会对全体组进行筛选(即每个组的全体);其参数必须是一个函数,函数参数是每个分组,并返回 True 或 False;若这个组有一个不满足条件,整个组都会被过滤掉了。类似 SQL 中 group by 后的 having 操作。其语法格式如下:
DataFrameGroupBy.filter(func, dropna=True, *args, **kwargs)
参数 | 参数说明 |
---|---|
func | func,应用于每个分组的子帧,返回 True or False |
dropna | 删除未通过筛选器的组;默认为 True,若为 False,则评估为 False 的组将填充 NaN |
data = {
'Team': ['Riders', 'Riders', 'Devils', 'Devils', 'Kings', 'Aliver', 'Kings', 'Kings', 'Riders', 'Royals', 'Royals',
'Riders'], 'Rank': [1, 2, 2, 3, 3, 4, 1, 1, 2, 4, 1, 2],
'Year': [2014, 2015, 2014, 2015, 2014, 2015, 2016, 2017, 2016, 2014, 2015, 2017],
'Points': [874, 789, 863, 663, 741, 802, 756, 788, 694, 701, 812, 698]}
df = pd.DataFrame(data)
# 筛选出参加比赛超过两次的球队(包含两次)
print(df.groupby('Team').filter(lambda x: len(x) > 3))
# 筛选出总分大于 2000 的球队
print(df.groupby('Team').filter(lambda x: x['Points'].sum() > 2000))
补充:对于 Series 和 DataFrame 的 filter 方法则有所不同,其是根据指定的索引标签对数据框行或列查询子集;此处不会对数据内容进行过滤,仅应用于按标签查询。其语法格式如下:
df.filter(items=None, like=None, regex=None, axis=None)
参数 | 参数说明 |
---|---|
items | list-like,对应轴的标签名列表 |
like | str,支持对应标签名的模糊名查询 |
regex | str(正则表达式),按正则表达式查询标签名 |
axis | int or str, 要查询的轴;‘index’ 为 Series,'columns’为 DataFrame;axis=0,为按行查询;axis=1,为按列查询 |
np.random.seed(50)
company = ["Aa", "Ba", "Cb", "Dc", "Ea", 'aD', "fG", "Hc"]
data = {
'Team': ['Riders', 'Riders', 'Devils', 'Devils', 'Kings', 'Aliver', 'Kings', 'Kings', 'Riders', 'Royals', 'Royals',
'Riders'], 'Rank': [1, 2, 2, 3, 3, 4, 1, 1, 2, 4, 1, 2],
'Year': [2014, 2015, 2014, 2015, 2014, 2015, 2016, 2017, 2016, 2014, 2015, 2017],
'Points': [874, 789, 863, 663, 741, 802, 756, 788, 694, 701, 812, 698]}
df = pd.DataFrame(data, index=[company[x] for x in np.random.randint(0, len(company), 12)])
# 按名称选择列
df.filter(items=['Team', 'Points'])
# 选择包含“a”的行
df.filter(like='a', axis=0)
# 索引中 a开头 列名有e的
df.filter(regex='^a', axis=0).filter(like='e', axis=1)
4. 数据操作
4.1 缺失值处理
4.1.1 检查缺失值
isnull() 和 notnull()
Pandas 提供了 isnull() 和 notnull() 两个函数,二者同时适用于 Series 和 DataFrame 对象。
df = pd.DataFrame(np.random.randint(10, 100, (5, 3)), index=['a', 'c', 'e', 'f', 'h'], columns=['one', 'two', 'three'])
df = df.reindex(['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
df[['one', 'two', 'three']].isnull()
4.1.2 填充缺失值
df.fillna()
df.fillna() 函数可以实现用非空数据“填充”NaN 值。其语法格式如下:
df.fillna(value, method, axis, inplace, limit, downcast)
参数 | 参数说明 |
---|---|
value | 填充的值,可以是一个标量,或 dict 等 |
method | 填充元素值:pad/ffill:向前填充值;bfill/backfill:向后填充值;None:指定一个值去替换缺失值(缺省默认这种方式) |
limit | 若 method 被指定,对于多段连续的空值区域,最多填充前 limit 个。若 method 未被指定, 在该 axis下,最多填充前 limit 个空值(不论空值连续区间是否间断) |
df.fillna(method='ffill', limit=2)
df.replace()
df.replace() 函数则将 DataFrame 中的通用值批量替换成特定值。其语法格式如下:
df.replace(to_replace, value, regex, inplace, limit, method)
参数 | 参数说明 |
---|---|
to_replace | 原数据表中要被替换的值 |
value | 替换后的值 |
regex | 使用正则表达式替换时,此参数必须设定为True |
# 替换指定的某个或多个数值(用字典)
# 字典的键作为原值,字典的值作为替换的新值
df.replace({np.NaN: 0}) # 等价于: df.replace([np.NaN], [0.])
-
读取含有“?”的数据时,可能会出现如下错误:
URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:833)>
解决办法:
# 全局取消证书验证 import ssl ssl._create_default_https_context = ssl._create_unverified_context
处理思路:
# 把一些其它值标记的缺失值,替换成np.nan wis = wis.replace(to_replace='?', value=np.nan) # 再进行缺失值处理 wis = wis.dropna()
4.1.3 删除缺失值
df.dropna()
df.dropna() 函数与参数 axis 实现删除缺失值操作。默认情况下,按照 axis=0 来按行处理,即若某一行中存在 NaN 值将会删除整行数据。其语法格式如下:
df.dropna(axis, how, thresh, subset, inplace)
参数 | 参数说明 |
---|---|
axis | 0为行,1为列,默认为 0,按行删除 |
how | any:删除带有 nan 的行;all:删除全为nan的行。默认any |
thresh | int,保留至少 thresh 个 非nan行 |
subset | list,在特定列缺失值处理,如:subset=[‘name’, ‘score’] |
# 保留至少有 2个 非NA值的行
df.dropna(thresh=2)
# 删除所有元素丢失的行
df.dropna(how='all')
4.2 离散与采样
4.2.1 数据离散化
pd.cut()
pd.cut() 函数实现了连续数据的离散化处理,即根据指定分界点对连续数据进行分箱处理。即将属性值域从最小值到最大值分成具有相同宽度的 n 个区间,n 由数据特点决定。比如属性值在[0,60]之间,最小值为0,最大值为60,要将其分为 3 等分,则区间被划分为[0,20] 、[21,40] 、[41,60],每个属性值对应属于它的那个区间。其语法格式如下:
pd.cut(x, bins, right, labels, retbins, precision=3, include_lowest, duplicates="raise", ordered=True)
参数 | 参数说明 |
---|---|
x | 1-D array |
bins | 整数,标量序列或者间隔索引,是进行分组的依据:若为整数n,则将 x 中的数值分成等宽的n份(每一组内的最大值与最小值之差约相等);若为标量序列,序列中的数值表示用来分档的分界值;若为间隔索引,必须不重叠 |
right | bool,默认为True,包含最右侧数值,即当“bins”=[1、2、3] 时表示(1,2],(2,3] |
labels | bool or array,指定分箱的标签;若为数组,长度要与分箱个数一致 |
retbins | bool,默认为False,是否显示分箱的分界值;当bins取整数时可设置为True以显示分界值,得到划分后的区间 |
precision | int,存储和显示分箱标签的精度 |
include_lowest | bool,默认为False,表示区间的左边是开还是闭 |
duplicates | 如果分箱临界值不唯一,则引发 ValueError 或丢弃非唯一(“drop”) |
# bins 取整数,即指定箱子个数
pd.cut(df.积分, bins=3, labels=["低","中","高"]) # 分成 3箱 并指定标签
pd.cut(df.积分, bins=3, labels=["低","中","高"], retbins=True) # retbins=True显示分界值
# bins 取标量序列
pd.cut(df.积分, [0,30,40,70], labels=["低","中","高"]) # 默认right = True
# 将结果写入原数据中
df.loc[:,"积分等级"]=pd.cut(df.积分,[0,30,40,70],labels=["低","中","高"], right=False)
pd.qcut()
pd.qcut() 函数也实现了 cut() 函数的类似功能,区别在于其可指定箱子的数量来对连续数据进行等宽分箱处理,即每个箱子中的数据量是相同的,比如有 60 个样本,要将其分为 k=3 部分,则每部分的长度为 20 个样本。其缺点是边界易出现重复值,若为了删除重复值可设置 duplicates=‘drop’,但易出现分片个数少于指定个数的问题。其一般会与 value_counts 搭配使用,统计每组的个数。其语法格式如下:
pd. qcut(x, q, labels, retbins=False, precision, duplicates="raise")
参数 | 参数说明 |
---|---|
x | 一维数组或Serise |
q | 分位数的整数或数组;若为分位数的整数,例如10用于十分位,4用于四分位;若为分位数数组,例如 [0, 0.25, 0.5, 0.75, 1] 用于四分位数 |
labels | array or bool,默认为None,用于指定每个箱体的标签;若为数组,长度要与分箱个数一致,如用四分位数分箱,需要指定四个标签 |
retbins | bool,默认为False,是否显示分箱的分界值; |
# 参数 q 控制箱子的个数以及分界值
pd.qcut(df.积分, q=4, labels=["低","中","高","很高"])
4.2.2 抽样与重采样
df.sample()
df.sample() 函数实现了随机抽样方法,即能从大量数据中快速地构建出一组数据分析模型。其语法格式如下:
df.sample(n, frac, replace=False, weights, random_state, axis=None)
参数 | 参数说明 |
---|---|
n | 要抽取的行数 |
frac | 抽取的比例,如 frac=0.5,代表抽取总体数据的 50% |
replace | bool,表示是否以有放回抽样的方式进行选择,默认为 False,取出数据后不再放回 |
weights | str or list,可选,代表每个样本的权重值(权重值越大被抽概率越高) |
random_state | 可选,控制随机状态,默认为 None,表示随机数据不会重复;若为 1 表示会取得重复数据 |
axis | 在哪个方向上抽取数据 (axis =1 表示列 / axis=0 表示行) |
dict = {'name': ["Jack", "Tom", "Helen", "John"], 'age': [28, 39, 34, 36], 'score': [98, 92, 91, 89]}
df = pd.DataFrame(dict)
# 默认随机选择两行
df.sample(n=2)
# 随机选择两列
df.sample(n=2, axis=1)
# 总体的50%
df.sample(frac=0.5)
# age 序列为权重值,并且允许重复数据出现
df.sample(n=2, weights='age', random_state=1)
df.resample()
df.resample() 函数实现了数据重采样(降采样),即可将时间序列数据从高频率(间隔短)转换为低频率(间隔长)数据。其语法格式如下:
df.resample(rule, axis=0, closed, label, convention='start', kind, loffset, base, on, level, origin='start_day', offset)
参数 | 参数说明 |
---|---|
rule | eDateOffset, Timedelta 或 str,表示目标转换的偏移字符串或对象(‘M’:按月) |
axis | {0 或 ‘index’, 1 或 ‘columns’}, 默认为0 |
closed | {‘right’, ‘left’},默认为None,bin区间的哪一边是关闭的;除’M’、‘A’、‘Q’、‘BM’、‘BA’、‘BQ’和’W’外,所有频率偏移的默认值都是’left’,它们的默认值都是’right’ |
label | {‘right’, ‘left’},默认为None,用哪边标签来标记bucket;除’M’、‘A’、‘Q’、‘BM’、‘BA’、‘BQ’和’W’外,所有频率偏移的默认值都是’left’,它们的默认值都是’right’ |
kind | {‘timestamp’, ‘period’},可选,默认为 None,传递 ‘timestamp’ 将结果索引转换为 DateTimeIndex,或period将其转换为 PeriodIndex;默认保留输入表示形式 |
origin | {‘epoch’, ‘start’, ‘start_day’},Timestamp 或 str, 默认为 ‘start_day’,调整分组的时间戳;原始时区必须与索引的时区匹配。 |
offset | Timedelta 或 str, 默认为 None,加到原点的偏移时间 |
rng = pd.date_range('1/1/2023', periods=100, freq='D')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
# 按天计数的频率转换为按月计数,降采样后并聚合
ts.resample('M').mean()
# 只想看到月份,可设置 kind=period
ts.resample('M', kind='period').mean()
升采样是将低频率数据(间隔长)转换为高频率(间隔短),可使用函数 asfreq() 实现:
# 生成一份时间序列数据(3天一次)
rng = pd.date_range('1/1/2023', periods=20, freq='3D')
ts = pd.Series(np.random.randn(len(rng)), index=rng)
# 使用 asfreq() 在原数据基础上实现频率转换(3D--->1D)
ts.resample('D').asfreq().head()
asfreq() 方法不仅能实现频率转换,还可保留原频率对应的数值,同时也可单独使用:
index = pd.date_range('1/1/2023', periods=6, freq='T')
series = pd.Series([0.0, None, 2.0, 3.0, 4.0, 5.0], index=index)
df = pd.DataFrame({'s': series})
print(df.asfreq("45s"))
但升采样会产生缺失值,因为原先采样数据的时间间隔长,升采样之后时间间隔短,有些时间点原先是没有采样数据的,故而会产生大量缺失值。此时就需要对这些缺失值进行处理—插值处理。
插值函数 | 函数说明 |
---|---|
pad/ffill | 用前一个非缺失值去填充缺失值 |
backfill/bfill | 用后一个非缺失值去填充缺失值 |
interpolater(‘linear’) | 线性插值方法 |
fillna(value) | 指定一个值去替换缺失值 |
# 3D--->1D:使用 ffill 处理插值
ts.resample('D').asfreq().ffill()
4.3 排序与去重
4.3.1 按标签排序
df.sort_index()
df.sort_index() 函数根据行标签对行排序,或根据列标签对列排序,或根据指定某列或某几列对行排序。 推荐只用 sort_index() 对“根据行标签”或“根据列标签”排序。其语法规则如下:
df.sort_index(axis, level, ascending, inplace, kind, na_position, by)
参数 | 参数说明 |
---|---|
axis | 0 按行名排序;1按列名排序,默认 axis=0 |
level | 若不为None,则对指定索引级别的值进行排序 |
ascending | 是否升序排列,默认为True,表示升序 |
inplace | 排序结果是否覆盖原表数据,默认为False,不覆盖 |
kind | 选择排序算法,{‘quicksort’, ‘mergesort’, ‘heapsort’} |
na_position | 缺失值默认排在最后{“first”,“last”} |
by | 按照 某一列或几列 数据进行排序(不建议使用) |
# 行标签乱序排列,列标签乱序排列
np.random.seed(100)
unsort_df = pd.DataFrame(np.random.randint(10, 100, size=(10, 5)), index=[1, 6, 4, 2, 3, 5, 9, 8, 0, 7],
columns=['col1', 'col2', 'col3', 'col4', 'col5'])
print(unsort_df)
# 按列标签降序排列
sort_df = unsort_df.sort_index(axis=1, ascending=False, )
print(sort_df)
-
注意:sort_index()函数只对标签进行排序。axis=1,此处按列标签的原因是:axis=1,本就是沿着水平方向的,水平方向是列标签所在位置;垂直方向是行标签所在位置。
4.3.2 按数值排序
df.sort_values()
df.sort_values() 函数既可根据列数据,也可根据行数据排序。其语法规则如下:
df.sort_values(by, axis=0, ascending=True, inplace=False, kind='quicksort', na_position='last')
参数 | 参数说明 |
---|---|
axis | {0 or ‘index’, 1 or ‘columns’},默认为 0,按列排序,即纵向排序;1 是横向排序 |
by | str or list of str;若 axis=0,by=“列名”;若 axis=1,by=“行名” |
# 先按 col2 列降序,再按 col5 列升序排序
sort_df = unsort_df.sort_values(by=['col2', 'col5'], axis=0, ascending=[False, True])
print(sort_df)
# 先按 1 行降序,再按 5 行升序排序
sort_df = unsort_df.sort_values(by=[1, 5], axis=1, ascending=[False, True])
print(sort_df)
- 注意1:必须指定by参数,即必须指定哪几行或哪几列;无法根据 行名和列名 排序。
- 注意2:指定多列(多行)排序时,先按排在前面的列(行)排序,若内部有相同数据,再对相同数据内部用下一列(行)排序,以此类推。若内部无重复数据,则后续排列不执行。
4.3.3 数据去重
df.drop_duplicates()
df.drop_duplicates() 函数能在一个数据集中,找出重复的数据并将其删除,最终只保存一个唯一存在的数据项。其语法格式如下:
df.drop_duplicates(subset, keep, inplace)
参数 | 参数说明 |
---|---|
subset | 要去重的列名,默认为 None,所有列参与去重 |
keep | 有三个可选参数,分别是 first、last、False,默认为 first,表示只保留第一次出现的重复项,删除其余重复项,last 表示只保留最后一次出现的重复项,False 则表示删除所有重复项 |
inplace | bool,默认为 False,表示删除重复项后返回一个副本,若为 Ture 则表示直接在原数据上删除重复项 |
df = pd.DataFrame({
'brand': ['Yum Yum', 'Yum Yum', 'Indomie', 'Indomie', 'Indomie'],
'style': ['cup', 'cup', 'cup', 'pack', 'pack'],
'rating': [4, 4, 3.5, 15, 5]
})
# subset 控制参与去重的列,参与的列元素相同则删除
print(df.drop_duplicates(subset=['brand', 'rating'], keep='first'))
4.4 数据统计
4.4.1 百分比变化
df.pct_change()
Series 和 DatFrames 都可使用 pct_change() 函数。该函数表示当前元素与先前元素的相差百分比,当指定periods=n时,表示当前元素与先前 第n个 元素的相差百分比。其语法格式如下:
df.pct_change(periods, fill_method, limit=None, freq=None)
参数 | 参数说明 |
---|---|
periods | 表示当前元素与先前 第periods个 元素的相差百分比 |
fill_method | 在计算百分比变化之前如何处理NaN,默认“pad” |
limit | 停止前需要连续填充的NaA数量 |
df = pd.DataFrame({
'2016': [1769950, 30586265],
'2015': [1500923, 40912316],
'2014': [1371819, 41403351]},
index=['GOOG', 'APPL'])
# 从右往左计算列间百分比变化
df.pct_change(axis='columns', periods=-1)
4.4.2 协方差
df.cov()
Series 对象提供了一个cov方法用来计算 Series 对象间的协方差。该方法会将缺失值自动排除。当应用于 DataFrame 时,协方差(cov)将计算所有列之间的协方差。
-
两个 Series 间:
s1 = pd.Series(np.random.randn(10)) s2 = pd.Series(np.random.randn(10)) print(s1.cov(s2))
-
一个 DataFrame 所有列间的协方差:
df.cov()
4.4.3 相关系数
df.corr()
相关系数显示任意两个 Series 间的线性关系。corr计算方法有: pearson、spearman 和 kendall。其语法格式如下:
df.corr(method='pearson', min_periods=1)
- method:可选值为{‘pearson’, ‘kendall’, ‘spearman’}
- pearson:衡量两个数据集合是否在一条线上面,即针对线性数据的相关系数计算,针对非线性数据便会有误差。
- kendall:用于反映分类变量相关性的指标,即针对无序序列的相关系数,非正太分布的数据。
- spearman:非线性的,非正太分析的数据的相关系数。
- min_periods:样本最少的数据量。
- 返回值:各类型之间的相关系数DataFrame表格。
import seaborn as sns
data = pd.DataFrame([[1, 6, 7, 5, 1], [2, 10, 8, 3, 4], [3, 4, 0, 10, 2]],
columns=['val1', 'val2', 'val3', 'val4', 'val5'])
# 各变量数据相关性的热力图
sns.heatmap(data.corr(), linewidths=0.1, vmax=1.0, square=True, linecolor='white', annot=True)
4.4.4 排名
df.rank()
df.rank() 函数会按照某种规则对序列中的元素值排名,并返回一个包含了原序列中每个元素值的名次的序列。其语法格式如下:
df.rank(axis=0, method, numeric_only, na_option, ascending, pct)
-
method:{‘average’,‘min’,‘max’,‘first’,‘dense’},默认为**‘average’**。
如何对具有相同值的记录组进行排名:
- average:组的平均等级
- min:组中最低排名
- max:组中最高等级
- first : 按排列顺序排列,依次排列
- dense:类似于 ‘min’,但组之间的排名始终提高1
-
numeric_only:bool, 可选,若为True,则仅对数字列进行排名。
-
na_option:{‘keep’,‘top’,‘bottom’},默认为**‘keep’**。
如何对NaN值进行排名:
- keep:将NaN等级分配给NaN值
- top:若升序,则将最小等级分配给NaN值
- bottom:若升序,则将最高等级分配给NaN值
-
pct:bool,默认为False,是否以百分比形式显示返回的排名。
# 按行排名,将相同数值设置为组中最大排名
df.rank(axis=1, method="max")
4.5 数据聚合
4.5.1 窗口函数
移动函数
df.rolling():移动窗口函数,它可与 mean、sum、median、std 等聚合函数一起使用,但 Pandas 为其定义了专门的聚合方法,如: rolling_mean()、rolling_count()、rolling_sum() 等。
其应用场景:现在有 10 天的销售额,而想每 3 天求一次销售总和,即第五天的销售额等于(第三天 + 第四天 + 第五天)的销售额之和,就得用移动窗口函数计算了。
其语法格式如下:
df.rolling(window, min_periods, center, win_type, on, axis=0, closed, step, method='single')
参数 | 参数说明 |
---|---|
window | int,默认为 1,表示窗口的大小,也就是观测值的数量 |
min_periods | int,最少需要有值的观测点的数量,默认与window相等 |
center | bool,默认为 False,是否把窗口的标签设置为居中 |
on | 可选参数,对 DataFrame,指定要计算滚动窗口的列,取值为列名 |
closed | 定义区间的开闭,支持 int 类型的 window,对于 offset类型 默认是左开右闭的,即默认为 right,可以根据情况指定为 left、both 等 |
参数详细说明可阅读博客:图解pandas窗口函数rolling。
# 生成时间序列
df = pd.DataFrame(np.random.randn(6, 4), index=pd.date_range('12/1/2020', periods=6), columns=['A', 'B', 'C', 'D'])
# 每3个数求一次均值
print(df.rolling(window=3).mean())
"""
window=3:表示每一列中依次紧邻的每 3 个数求一次均值.当不满足 3 个数时,所求值均为 NaN 值,因此前两列的值为 NaN,直到第三行值才满足要求 window =3.
求均值的公式如下所示:(index1+index2+index3)/3
"""
输出结果如下:
A B C D
2020-12-01 NaN NaN NaN NaN
2020-12-02 NaN NaN NaN NaN
2020-12-03 0.973848 -0.161493 -0.013272 0.187942
2020-12-04 -0.157035 0.173315 -1.030933 -0.702870
2020-12-05 -0.234948 -0.006002 -0.516652 -0.295033
2020-12-06 -0.759595 -0.932250 -0.139827 -0.329437
df = pd.DataFrame(np.random.randint(1, 10, size=(5, 2)), index=pd.date_range('1/1/2023', periods=5), columns=['A', 'B'])
# 将A列最近2个值相加并生成新列
df['C'] = df['A'].rolling(window=2).sum()
# 将B列最近3个值相加求平均并生成新列
df['D'] = df['A'].rolling(window=3).mean()
print(df)
输出结果如下:
A B C D
2023-01-01 9 9 NaN NaN
2023-01-02 4 8 13.0 NaN
2023-01-03 8 1 12.0 7.000000
2023-01-04 5 3 13.0 5.666667
2023-01-05 6 3 11.0 6.333333
扩展函数
df.expanding():扩展窗口函数,扩展是指由序列的第一个元素开始,逐个向后计算元素的聚合值,即df.expanding() 函数只设置最小的观测值数量,不固定窗口大小,实现累计计算,即不断扩展。
expanding()函数,类似 cumsum() 函数的累计求和,其优势在于还可以进行更多的聚类计算;当rolling() 函数的参数 window=len(df) 时,实现的效果与 expanding() 函数是一样的。
其语法格式如下:
df.expanding(min_periods=1, center=False, axis=0, method='single')
参数 | 参数说明 |
---|---|
min_periods | int,窗口中具有值的最小观察数(否则结果为NA) |
center | bool,默认为 False,是否将标签设置在窗口的中央 |
df = pd.DataFrame({'A': [2, 1, 2, 3, 4]})
df['B'] = df['A'].expanding(2).sum()
df['C'] = df['A'].cumsum(axis=0)
输出结果如下:
A B C
0 2 NaN 2
1 1 3.0 3
2 2 5.0 5
3 3 8.0 8
4 4 12.0 12
设置 min_periods=0,表示至少 2 个数求一次和,
计算方式为 (index0+index1),而index2的计算方式是 (index0+index1+index2,依次类推。
指数加权函数
df.ewm() 函数表示指数加权移动,先会对序列元素做指数加权运算,其次计算加权后的均值。该函数通过指定 com、span 或 halflife 参数来实现指数加权移动。
该函数 没怎么遇到过,此处不做介绍,可以看如下网站的介绍:
4.5.2 聚合函数
agg()
其语法格式如下:
df.agg(func, axis=0, *args, **kwargs)
df = pd.DataFrame(np.random.randn(5, 3), index=pd.date_range('1/1/2023', periods=5), columns=['A', 'B', 'C'])
# 对多列数据聚合
df[['A', 'B']].agg(np.sum)
# 对不同列应用多个函数
df[['A', 'B']].agg([np.sum, np.mean])
# 针对某列运用聚合函数
df.agg({'A': np.sum, 'B': np.mean})
# 自定义函数计算极差
df['C'].agg(lambda x:x.max()-x.min())
# 带参数
def f(df, low, high):
return df.between(low, high).max()
dfgroup['C'].agg(f,1,20)
注:可利用 NamedAgg 函数进行多个聚合,但其不支持lambda函数,可使用外置的 def。
def R1(x):
return x.max() - x.min()
def R2(x):
return x.max() - x.median()
grouped_single['col'].agg(min_score1=pd.NamedAgg(column='col1', aggfunc=R1),
max_score1=pd.NamedAgg(column='col2', aggfunc='max'),
range_score2=pd.NamedAgg(column='col3', aggfunc=R2)).head()
apply()
对于分组后使用 apply() 函数进行数值分析的调用原理,简单说就是:针对分组后的 每个子DataFrame,调用自定义函数,分别计算每个组内数据对应值,然后返回对应值所在的那条记录。其使用案例如下:
# 获取各个公司年龄最大的员工的数据
company = ["A", "B", "C", "D"]
np.random.seed(50)
df = pd.DataFrame({"company": [company[x] for x in np.random.randint(0, len(company), 10)],
"salary": np.random.randint(5, 50, 10), "age": np.random.randint(18, 30, 10)})
def get_oldest_staff(x): # x: 就是子DataFrame
df = x.sort_values(by = 'age',ascending=False)
return df.iloc[0,:]
# as_index=False:不返回带有组标签作为索引的对象,即与原数据表索引一致了
oldest_staff = df.groupby('company',as_index=False).apply(get_oldest_staff)
注:虽然说apply拥有更大的灵活性,但apply的运行效率会比agg和transform更慢。所以,groupby之后能用agg和transform解决的问题还是优先使用这两个方法,实在解决不了了才考虑使用apply进行操作。
可参考博客:Pandas分组、聚合、过滤操作全面解析,了解详细使用案例。
5. 其他函数
5.1 描述性统计
函数名称 | 函数说明 |
---|---|
df.count() | 统计某个非空值的数量 |
df.sum() | 求和 |
df.mean() | 求均值 |
df.median() | 求中位数 |
df.mode() | 求众数 |
df.std() | 求标准差 |
df.min() | 求最小值 |
df.max() | 求最大值 |
df.abs() | 求绝对值 |
df.prod() | 求所有数值的乘积 |
df.cumsum() | 计算累计和 |
df.cumprod() | 计算累计积 |
df.corr() | 计算数列或变量间的相关系数,取值-1到1,值越大表示关联性越强 |
df.describe() | 显示与 DataFrame 数据列相关的统计信息摘要 |
注意事项
df.describe() 函数输出了平均值、std 和 IQR 值等一系列统计信息。通过其提供的include能够筛选字符列或数字列的摘要信息。include 相关参数值说明如下:
- object: 表示对字符列进行统计信息描述;
- number:表示对数字列进行统计信息描述;
- all:汇总所有列的统计信息。
在 DataFrame 中,使用聚合类方法需要指定轴(axis)参数,其有两种传参方式:
- axis=0,按列,即聚合每列数据(默认);
- axis=1,按行,即聚合每行数据。
5.2 数据索引
5.2.1 重置索引
df.set_index()
df.set_index() 函数可对原数据表临时的重新构建列索引。其语法格式如下:
df.set_index(keys, drop, append, inplace=False, verify_integrity=False)
参数 | 参数说明 |
---|---|
keys | name or list of name,需要设置为索引的列 |
drop | bool,默认为True,原表中删除用作新索引的列 |
append | bool,默认为False,是否将列附加到现有索引 |
inplace | bool,默认为False,当前操作是否对原数据生效 |
verify_integrity | bool,默认为False,检查新索引的副本,否则,请将检查推迟到必要时进行;将其设置为 False 将提高该方法的性能 |
df = pd.DataFrame({'Country': ['China', 'China', 'India', 'India', 'Japan', 'China', 'India'], 'Income': [10000, 10000, 5000, 5002, 4000, 8000, 5000], 'Age': [40, 43, 34, 32, 25, 25, 22]})
# 将'Country'作为列索引,原表数据删除 'Country'列
df_new = df.set_index('Country', drop=True)
# 将'Country'作为列索引,原表数据删除'Country'列; 要求:原来索引和新索引都被保留
df_new = df.set_index('Country', drop=True, append=True)
df.reset_index()
df.reset_index() 函数用于还原数据表的索引,还原分为两种类型,第一种是对原来的数据表进行 reset;第二种是对使用过 df.set_index() 函数的数据表进行 reset。其语法格式如下:
df.reset_index(level, drop=False, inplace, col_level=0, col_fill='')
参数 | 参数说明 |
---|---|
level | int、str、tuple or list,默认无,仅从索引中删除给定级别;默认移除所有级别;控制了具体要还原的那个等级的索引 |
drop | bool,默认为False:索引列会被还原为普通列;若为True:经设置后的新索引值被会丢弃 |
inplace | bool,默认为False,当前操作是否对原数据生效 |
col_level | int or str,默认0,若列有多个级别,则确定将标签插入到哪个级别;默认情况下,将插入到第一级 |
col_fill | 默认‘’,若列有多个级别,则确定其他级别的命名方式;若没有,则重复索引名 |
df = pd.DataFrame({'Country': ['China', 'China', 'India', 'India', 'Japan', 'China', 'India'], 'Income': [10000, 10000, 5000, 5002, 4000, 8000, 5000], 'Age': [40, 43, 34, 32, 25, 25, 22]})
# 将 'Country' 作为列索引,原表数据删除 'Country'列
df_new = df.set_index('Country', drop=True)
# 用 reset_index() 函数进行还原
df_new01 = df_new.reset_index(drop=False)
# 当 drop=True 时,刚刚经过处理后的新索引 'Country' 被删除了
df_new02 = df_new.reset_index(drop=True)
对原来的数据表进行 reset 处理:原来的数据表的索引是默认生成的 RangeIndex。
df = pd.DataFrame({'Country': ['China', 'China', 'India', 'India', 'Japan', 'China', 'India'], 'Income': [10000, 10000, 5000, 5002, 4000, 8000, 5000], 'Age': [40, 43, 34, 32, 25, 25, 22]})
df_new = df.reset_index(drop=False)
输出结果如下:
index Country Income Age
0 0 China 10000 40
1 1 China 10000 43
2 2 India 5000 34
3 3 India 5002 32
4 4 Japan 4000 25
5 5 China 8000 25
6 6 India 5000 22
此时数据表在原有基础上,添加了列名为 index 的新列,同时在新列上进行重置索引。
df.reindex()
df.reindex() 函数从原表中按照行列索引提取相关行列数据,默认返回一份新表;若索引不存在,则均填充NaN。
N = 5
df = pd.DataFrame({
'A': pd.date_range(start='2016-01-01', periods=N, freq='D'),
'x': np.linspace(0, stop=N - 1, num=N),
'y': np.random.rand(N),
'C': np.random.choice(['Low', 'Medium', 'High'], N).tolist(),
'D': np.random.normal(100, 10, size=(N)).tolist()
})
print(df)
# 重置行,列索引标签 (默认: 深拷贝)
df_reindex = df.reindex(index=[0, 2, 'a'], columns=['A', 'C', 'B'])
print(df_reindex)
"""
A x y C D
0 2016-01-01 0.0 0.861422 Medium 95.003082
1 2016-01-02 1.0 0.500416 Low 111.794800
2 2016-01-03 2.0 0.276714 Low 85.913680
3 2016-01-04 3.0 0.117294 Medium 119.182945
4 2016-01-05 4.0 0.878774 Low 82.180119
A C B
0 2016-01-01 Medium NaN
2 2016-01-03 Low NaN
a NaT NaN NaN
"""
df.reindex_like()
df.reindex_like() 函数则是在两个列索引标签一致的表上(列索引必须相同),让其中一份表的行索引按照另一份表的行索引进行重建。其语法格式如下:
df.reindex_like(other, method, copy, limit, tolerance)
参数 | 参数说明 |
---|---|
other | 按照 other 表的行索引进行重建 |
method | 填充元素值:pad/ffill:向前填充值;bfill/backfill:向后填充值;nearest:从距离最近的索引值开始填充 |
copy | 是否进行深拷贝,默认为True |
limit | 控制填充的最大行数 |
a = pd.DataFrame(np.arange(1, 16).reshape(5, 3), columns=['col1', 'col2', 'col3'])
b = pd.DataFrame(np.arange(20, 41).reshape(7, 3), columns=['col1', 'col2', 'col3'])
# 多的舍弃,不够填充 NaN
a = a.reindex_like(b)
# 向前填充值
c = a.reindex_like(b, method='ffill')
# 向前填充值,限制填充最大行数
d = a.reindex_like(b, method='ffill', limit=1) # 还会有一行 NaN
5.2.2 标签重命名
df.rename()
df.rename() 函数可以对行、列标签进行重新命名。其语法格式如下:
df.rename(index, columns, axis, copy, inplace, level, errors)
参数 | 参数说明 |
---|---|
index | 可传入字典,重命名行索引 |
columns | 可传入字典,重命名列索引 |
copy | 是否进行深拷贝,默认为True |
inplace | 是否覆盖原来的数据,默认False;若为True,则忽略copy |
df = pd.DataFrame(np.arange(1, 16).reshape(5, 3), columns=['col1', 'col2', 'col3'])
# 对行和列重新命名
df_new = df.rename(columns={'col1': 'c1', 'col2': 'c2'}, index={0: 'apple', 1: 'banana', 2: 'durian'})
# 直接在原表上修改
df.rename(columns={'col1': 'c1', 'col2': 'c2'}, index={0: 'apple', 1: 'banana', 2: 'durian'}, inplace=True)
5.3 数据格式化
解决数据体量较大时出现的输出内容不全(部分内容省略)或换行错误等问题。
函数名称 | 函数说明 |
---|---|
pd.get_option() | 接收单一参数,用来获取显示上限的行数或列数 |
pd.set_option() | 更改解释器的默认参数值(行数、列数) |
pd.reset_option() | 接受一个参数,并将修改后的值设置回默认值 |
pd.describe_option() | 输出参数的描述信息 |
pd.option_context | 临时设置解释器参数,当退出使用的语句块时,恢复为默认值 |
pd.option_context() 用于临时设置 with 语句块中默认显示的参数。当退出 with 语句块时,参数值会自动恢复。
with pd.option_context("display.max_rows",10):
print(pd.get_option("display.max_rows"))
print(pd.get_option("display.max_rows"))
参数 | 说明 |
---|---|
display.max_rows | 最大显示行数,超过该值用省略号代替,为None时显示所有行 |
display.max_columns | 最大显示列数,超过该值用省略号代替,为None时显示所有列 |
display.expand_frame_repr | 输出数据宽度超过设置宽度时,表示是否对其折叠,False不折叠,True折叠 |
display.max_colwidth | 单列数据宽度,以字符个数计算,超过时用省略号表示 |
display.precision | 设置输出数据的小数点位数 |
display.width | 数据显示区域的宽度,以总字符数计算 |
display.show_dimensions | 当数据量大需要以truncate(带引号的省略方式)显示时,该参数表示是否在最后显示数据的维数,默认 True 显示,False 不显示 |
5.4 迭代表格
函数名称 | 函数说明 |
---|---|
df.items() | 以键值对 (key,value) 的形式遍历表格 |
df.iterrows() | 以 (row_index,row) 的形式遍历行 |
df.itertuples() | 使用已命名元组的方式对行遍历 |
df = pd.DataFrame(np.arange(1, 16).reshape(5, 3), columns=['col1', 'col2', 'col3'])
for key, value in df.items():
print(key, "\n", value)
for row_index, row in df.iterrows():
print(row_index, "\n", row)
for row in df.itertuples():
print(row)
注:迭代器返回原对象副本,所以在迭代过程中修改元素值,不会影响原对象。
5.5 字符串处理
以下各函数均在 Series/DataFrame.str.* 中。
函数名称 | 函数说明 |
---|---|
lower() | 将字符串转为小写 |
upper() | 将字符串转为大写 |
len() | 计算字符串长度 |
strip() | 去除字符串两边的空格(包含换行符) |
split() | 用指定的分割符分割字符串 |
cat(sep=“”) | 用给定的分隔符连接字符串元素 |
get_dummies() | 返回一个带有独热编码值的 DataFrame 结构 |
contains(pattern) | 若子字符串包含在元素中,则为每个元素返回一个布尔值 True,否则为 False |
replace(a,b) | 将值 a 替换为值 b |
count(pattern) | 返回每个字符串元素出现的次数 |
startswith(pattern) | 若 Series 中的元素以指定的字符串开头,则返回 True |
endswith(pattern) | 若 Series 中的元素以指定的字符串结尾,则返回 True |
findall(pattern) | 以列表的形式返回出现的字符串 |
swapcase() | 交换大小写 |
islower() | 返回布尔值,检查 Series 中组成每个字符串的所有字符是否都为小写 |
issupper() | 返回布尔值,检查 Series 中组成每个字符串的所有字符是否都为大写 |
isnumeric() | 返回布尔值,检查 Series 中组成每个字符串的所有字符是否都为数字 |
repeat(value) | 以指定的次数重复每个元素 |
find(pattern) | 返回字符串第一次出现的索引位置 |
6. 实战案例
案例以全国大学生数学建模国赛2020年E题为例,因赛题数据处理较为复杂,还涉及了预测分析算法,故只提取部分进行练习。
又因为时序数据非常重要,但限于篇幅没有展开,打算另行好好总结一下,此处不再赘述。
6.1 数据描述
四季度不同地方水表用水量记录,四张表,每张表信息大致如下:
表头 | (水表名,采集时间,上次读数,当前读数,用量) |
---|---|
采集时间 | 开始:2019/1/1 00:00:00 结束:2019/12/31 23:45:00 每 15min 记录一次 |
数据量 | 分四个季度,即四张 excel 表,每表为每个季度记录数,数据量达近 80 万条 |
水表层级关系表:
6.2 表的数据处理
6.2.1 表的合并
将记录用水量的四张表与水表层级关系借助 merge 函数进行合并:
import os
# 纵向合并四个季度的水表数据
# 定义需要读取excel文件的存放路径
inputdir = r'D:\GL\2020_E\datas1'
# 根据文件里表格表头,创建一个空DataFrame
df_empty = pd.DataFrame(columns=['水表名', '水表号', '采集时间', '上次读数', '当前读数', '用量'])
# 读取文件夹中 4个 excel文件
for parents, dirnames, filenames in os.walk(inputdir):
for filename in filenames:
df = pd.read_excel(os.path.join(parents, filename))
df_empty = pd.concat([df_empty, df], ignore_index=True)
data_index = pd.read_excel('../datas2/附件_水表层级.xlsx')
# 删除缺失值,此处每行至少保留7个数据,并对原表进行修改(由观察水表层级表所得,因为每个水表只对应一个层级)
data_index.dropna(axis=0, thresh=7, inplace=True)
data_all = pd.merge(df_empty, data_index, how="outer")
data_all.index.name = '序号'
# 用量是否为表显示数据差
data_all['表显差值'] = data_all['当前读数'] - data_all['上次读数']
data_all['差值'] = data_all['表显差值'] - data_all['用量']
data_all.to_csv('../datas3/功能区关联表.csv')
6.2.2 分级统计水量
水表有四个级别,以一级水表为例,统计其总用水量:
data_all = pd.read_csv('../datas3/功能区关联表.csv')
data_all['采集时间'] = to_datetime(data_all.采集时间, format='%Y/%m/%d %H:%M:%S')
# 一级水表
sbm_1 = data_all[data_all['一级表计编码'].notnull()] # 将非空值数据提取出来
sbm_1_y1 = pd.DataFrame(columns=sbm_1['一级表计编码'].unique())
sbm_1_y115 = pd.DataFrame(columns=sbm_1['一级表计编码'].unique())
for i in sbm_1['一级表计编码'].unique():
temp = sbm_1[sbm_1['一级表计编码'] == i]
temp.set_index('采集时间', inplace=True)
sbm_1_y1[i] = temp['用量'].resample('D').sum()
sbm_1_y115[i] = temp['用量'].resample('15min').sum()
# 一级总用水量
for i in sbm_1['一级表计编码'].unique():
temp = sbm_1_y1[i]
temp[temp > 5 * temp.mean()] = temp.mean()
sbm_1_y1[i] = temp
sbm_1_y1 = sbm_1_y1.fillna(0)
zysl_yj = (sbm_1_y1.sum(axis=0)).sum()
print(zysl_yj)
四级水表数据,concat 水平拼接后部分输出结果如下:
对数据进行可视化后就能大致发现何时哪个级别水表有漏水现象,以便快速定位水表位置:
6.2.3 分时分项统计
12个功能区按工作日统计用水量:
data_all = pd.read_csv('../datas3/功能区关联表.csv')
data_all['采集时间'] = to_datetime(data_all.采集时间, format='%Y/%m/%d %H:%M:%S')
# 各水表总用水量统计
sbm_sl = data_all['用量'].groupby(by=data_all['水表号']).sum()
# 12个工作区总用水量
gnq_sl = data_all['用量'].groupby(by=data_all['功能分类']).sum()
gnq_sl.to_excel('../datas4/各功能区总用水量.xlsx')
# 12个功能区
gnq = ['宿舍', '教学楼', '办公室', '食堂', '科研楼', '运动场所', '后勤保卫', '校医院', '酒店', '图书馆', '其他']
# 12个功能区按工作日统计用水量
gnq_day5 = pd.DataFrame(columns=['宿舍', '教学楼', '办公室', '食堂', '科研楼', '运动场所', '后勤保卫', '校医院', '酒店', '图书馆', '其他'])
for i in np.arange(11):
temp = data_all[data_all['功能分类'] == gnq[i]]
# 使用现有列设置单(复合)索引
temp.set_index('采集时间', inplace=True)
gnq_day5.iloc[:, i] = temp['用量'].resample('B').sum()
gnq_day5.to_excel('../datas4/功能区每5天用水量.xlsx')
部分输出结果如下: