对《对比Excel,轻松学习Python数据分析》pandas操作部分的整理
前言
本文基于《对比Excel,轻松学习Python数据分析》,对该书的pandas部分进行整理,舍弃了语法基础部分。
由于这本书的其他部分(可视化部分、进阶部分)的解决方案在对实际应用场景中的说明比较简略,因此在这里也省略。
暂时没有写Excel的实现。
本文只列出了常见的函数和参数,其他详见pandas的官方中文文档https://www.pypandas.cn/。
本文结构
文章目录
pandas操作部分
pandas设置
一般约定pandas为pd,即
import pandas as pd
以下的代码能够更好地显示结果,其他设置可自行搜索
# 设置列宽,解决字段内容过多显示不全问题
pd.set_option("display.max_colwidth", 100)
# 设置对齐
pd.set_option('display.unicode.ambiguous_as_wide', True)
pd.set_option('display.unicode.east_asian_width', True)
pd.set_option('display.colheader_justify', 'left')
数据导入
外部数据中常见的格式为.xlsx(.xls)和.csv,对应的导入函数为Pandas.read_excel()和Pandas.read_csv()。
注:
- 当调用read_excel()可能会提示缺少openpyxl库,装上就不会报错了。
- read_csv()导入数据时经常出现编码方面的报错,常见的编码格式为gbk和utf-8,报错就换另一个。
下面导入了两个不同格式文件的数据:
filePath1 = r'../对比Excel,轻松学习Python数据分析数据集/catering_fish_congee.xls'
filePath2 = r'../对比Excel,轻松学习Python数据分析数据集/loan.csv'
# 导入.xlsx(.xls)
f1: pd.DataFrame = pd.read_excel(filePath1, sheet_name='Sheet1', index_col=0)
# 导入.csv
f2: pd.DataFrame = pd.read_csv(filePath2, encoding='gbk', header=0)
数据导出
这里看实参大概就知道是什么作用了。
- index=False表示导出不带索引,默认是带的(True)
- 默认输出文件到py文件同目录下
f1.to_excel(excel_writer=r"newF1.xlsx")
f2.to_csv(path_or_buf=r"newF2.csv", encoding='gbk', index=False)
新建DataFrame(简称df)
如果你手头上没有适合的数据,且现在需要新建一个数据框来验证你的想法,这里推荐两种方式:
- 字典创建
- 列表创建
# 传入字典
dataDict = {"姓名": ["张三", "李四", "王五", ], "组号": [1, 2, 3, ]}
# 传入嵌套列表
tempData1 = pd.DataFrame(dataDict)
print(tempData1)
dataList = [
["姓名", "组号"],
["张三", 1],
["李四", 2],
["王五", 3],
]
tempData2 = pd.DataFrame(dataList)
print(tempData2)
姓名 组号
0 张三 1
1 李四 2
2 王五 3
0 1
0 姓名 组号
1 张三 1
2 李四 2
3 王五 3
两种创建方法在索引上有一些不同。
简单预览
导入数据后我们一般都习惯简单看一下数据的结构以及数值分布的情况:
预览前几行
df.head()
head()方法只有一个参数n,用于控制显示前几行(默认5)。
获取数据表大小(即多少行多少列)
df.shape
shape方法被定义为只读方法,因此它不需要加括号。
结果返回一个元祖,例如该数据表有150000行,6列:
(150000, 6)
获取数据类型
df.info()
例如:
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 150000 entries, 0 to 149999
Data columns (total 6 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 用户ID 150000 non-null int64
1 好坏客户 150000 non-null int64
2 年龄 150000 non-null int64
3 负债率 150000 non-null float64
4 月收入 120269 non-null float64
5 家属数量 146076 non-null float64
dtypes: float64(3), int64(3)
memory usage: 6.9 MB
上面给出的信息解释为:
- df的行索引index是0~149999,共150000行
- df总共有6列(0~5)
- Column:列名
- Non-Null Count:非空值计数
- Dtype:数据类型
获取数值分布情况
df.describe()
该方法会自动识别数据表中的数值类型数据。
用户ID 好坏客户 ... 月收入 家属数量
count 150000.000000 150000.000000 ... 1.202690e+05 146076.000000
mean 75000.500000 0.066840 ... 6.670221e+03 0.757222
std 43301.414527 0.249746 ... 1.438467e+04 1.115086
min 1.000000 0.000000 ... 0.000000e+00 0.000000
25% 37500.750000 0.000000 ... 3.400000e+03 0.000000
50% 75000.500000 0.000000 ... 5.400000e+03 0.000000
75% 112500.250000 0.000000 ... 8.249000e+03 1.000000
max 150000.000000 1.000000 ... 3.008750e+06 20.000000
- 计数
- 均值
- 标准差
- 最小值
- 25%分位数
- 中位数
- 75%分位数
- 最大值
数值操作
数据导入只是数据分析的开头。在大部分时间内我们都会在进行数据清洗,以便满足后续的建模和分析。在数据清洗、分析的过程中,我们会进行频繁的数值操作。
查找、选择
查找操作一般用到两个方法:
# 通过索引名称查找
df.loc["行索引名称", "列索引名称"]
df.loc[["第一行", "第二列"], ["第三列", "第四列"]]
# 通过位置查找
df.iloc[0, 1]
df.iloc[[0, 5], [1, 3]]
df.iloc[0:5, 1:3]
df.iloc[range(0, 5), range(1, 3)]
条件查找(布尔索引):
# 单条件
# 例如我需要找出数据表中
# 月薪在3k及以上的用户,他们的ID及负债率
df[df["月收入"] >= 3000][["用户ID", "负债率"]]
# 多条件
# 在前面的条件下在加个年龄在30岁以下的限定
df[(df["月收入"] >= 3000) | (df['年龄'] <= 30)][["用户ID", "负债率"]]
# 多条件的布尔索引格式为:(条件1 | 条件2)
插入、替换、删除
以该数据为例:
姓名 组号
0 张三 1
1 李四 2
2 王五 3
插入操作:
插入列有两种方法:
- 在尾部新建一列
- 在指定位置插入
# 在指定位置插入
df.insert(loc=1, column="识别码", value=[11, 22, 33])
# loc控制插入的列位置
# 在尾部新建
df["性别"] = ["男", "女", "男"]
插入行、多行、多列见表的拼接
替换操作:
- 一对一替换
- 多对一替换
- 多对多替换
# 一对一
# 把数据表中年龄这一列的30岁全部替换成29岁,并且原地更新(inplace=True)
df["年龄"].replace(30, 29, inplace=Ture)
# 把全表的缺失值全部替换成0
import numpy as np
df.replcae(np.NaN, 0, inplace=True)
# 多对一
# 多对一就是把一个范围内的值换成一个值
# 比如我想把30岁、29岁、28岁替换成20岁,那么
df["年龄"].replace([30, 29, 28], 20, inplace=True)
# 多对多
# 用a替换A,b替换B
df.replace({
"A": "a",
"B": "b",
})
删除操作
删除操作用到的最多的是drop()方法
# 删除列
# 传入列名
df.drop(["列名1", "列名2"], axis=1)
# 传入位置
df.drop(df.columns[[4, 5]], axis=1)
# 传入列表
df.drop(columns=["列名1", "列名2"])
# 删除行
# 传入行索引名称
df.drop(["行索引名称1", "行索引名称2"], axis=0)
# 传入行号
df.drop(df.index[[0, 1]], axis=0)
# 传入列表
df.drop(index=["行索引名称1", "行索引名称2"])
# 删除指定行
# 一般通过筛选操作替代删除满足条件的值的操作
# 例如:删除40岁及以上的值=筛选出40岁以下的值
df[df["年龄"]<40]
删除缺失值
# 删除含有na值的行
df.dropna()
# 删除空白行
df.dropna(how="all")
计数、切分、排序
以如下的数据表为例
用户ID 好坏客户 年龄 负债率 月收入 家属数量
0 1 1 45 0.802982 9120.0 2.0
1 2 0 40 0.121876 2600.0 1.0
2 3 0 38 0.085113 3042.0 0.0
3 4 0 30 0.036050 3300.0 0.0
4 5 0 49 0.024926 63588.0 0.0
...
数值计数(value_counts()方法)
# 对表中数据家属数量的出现次数进行计数
df["家属数量"].value_counts()
结果如下:
家属数量
0.0 86902
1.0 26316
2.0 19522
...
Name: count, dtype: int64
以占比的形式显示:
df["家属数量"].value_counts(normalize=True)
家属数量
0.0 0.594909
1.0 0.180153
2.0 0.133643
...
Name: count, dtype: int64
切分
注:切分但不进行计数
cut方法(需要手动输入切分区间):
对表中客户的年龄进行切分
pd.cut(df["年龄"], bins=[0, 18, 30, 45, 65])
0 (30.0, 45.0]
1 (30.0, 45.0]
2 (30.0, 45.0]
...
149998 (18.0, 30.0]
149999 (45.0, 65.0]
Name: 年龄, Length: 150000, dtype: category
Categories (4, interval[int64, right]): [(0, 18] < (18, 30] < (30, 45] < (45, 65]]
qcut方法(自动切分区间,但需要指明切分区间):
pd.qcut(df["年龄"], 5)
0 (39.0, 48.0]
1 (39.0, 48.0]
2 (-0.001, 39.0]
...
149998 (-0.001, 39.0]
149999 (56.0, 65.0]
Name: 年龄, Length: 150000, dtype: category
Categories (5, interval[float64, right]): [(-0.001, 39.0] < (39.0, 48.0] < (48.0, 56.0] <
(56.0, 65.0] < (65.0, 109.0]]
如上可见,当数据分布不均匀时,qcut()方法产生的结果可能与实际情况不太相符,因此还是建议手动切分区间。
排序
# 按照一列数值排序
df.sort_values(by=["列名1"], asscending=False) # asscending=False意味降序排序,True升序
# 按照多列数值排序
df.sort_values(by=["列名1", "列名2"], asscending=False)
多表拼接
单纯拼接多表
-
objs参数设置需要连接的对象集合,一般是列表或字典
-
axis参数设置拼接方向,默认为0(纵向拼接),1为横向拼接
dataDict1 = {
"姓名": ["张三", "李四", "王五"],
"年龄": [17, 18, 17],
"班级": [1, 1, 2]
}
dataDict2 = {
"成绩": [78, 79, 76]
}
df1 = pd.DataFrame(dataDict1)
df2 = pd.DataFrame(dataDict2)
newDf = pd.concat(objs=[df1, df2], axis=1)
姓名 年龄 班级
0 张三 17 1
1 李四 18 1
2 王五 17 2
以公共列作为连接键的拼接
dataDict1 = {
"姓名": ["张三", "李四", "王五"],
"年龄": [17, 18, 17],
"学号": ["101", "102", "103"]
}
dataDict2 = {
"学号": ["101", "102", "103"],
"成绩": [78, 79, 76]
}
df1 = pd.DataFrame(dataDict1)
df2 = pd.DataFrame(dataDict2)
newDf = pd.merge(df1, df2, on="学号")
姓名 年龄 学号
0 张三 17 101
1 李四 18 102
2 王五 17 103
merge()方法的参数说明:
- on:指定连接键
- 当实际值一样、列名不同的情况是,分别用left_on和right_on指定左、右表用作连接键的列名
- 当公共列是索引列时,用left_index和right_index控制左右表的索引
- 其他略
行列互换(转置)、索引重塑、长宽表转换
行列互换
df.T
索引重塑
即把数据从表格型数据转换为树型数据
# 表格型
用户ID 好坏客户 年龄 负债率 月收入 家属数量
0 1 1 45 0.802982 9120.0 2.0
1 2 0 40 0.121876 2600.0 1.0
2 3 0 38 0.085113 3042.0 0.0
3 4 0 30 0.036050 3300.0 0.0
4 5 0 49 0.024926 63588.0 0.0
# 树型
0 用户ID 1.000000
好坏客户 1.000000
年龄 45.000000
负债率 0.802982
月收入 9120.000000
...
149999 好坏客户 0.000000
年龄 64.000000
负债率 0.249908
月收入 8158.000000
家属数量 0.000000
# 从表格型数据转换为树型数据
df.stack()
# 从树型数据转换为表格型数据
df.unstack()
长宽表转换
宽表转换成长表(melt()方法)
df.melt(id_vars=["保留的列名1", "保留的列名2"])
长表转换成宽表(privot_table()方法
df.privot_table(index=["设置为行索引的列名1", "设置为行索引的列名2"], columns="设置为列索引的列名", value="值")
这部分详见数据透视表。
索引设置
设置索引列
df.set_index('索引列名')
重置索引
df.reset_index()
- level指定将第几级的索引转化为columns,默认全部转化
- drop是否将原索引删除,默认为False
- inplace是否原地修改
数据分组/数据透视表
这部分在excel中还是挺常见的。
数据分组
按照一列进行分组
# 获取DataFrameGroupBy对象,包含分组以后的若干组数据
dfGoupBy = df.gropby(by="传入的列名")
# 进行计数运算并合并
dfGroupBy.count()
# 进行求和运算并合并
dfGroupBy.sum()
以下面的数据表为例,by参数传入好坏客户
好坏客户 家属数量
0 1 2.0
1 0 1.0
2 0 0.0
3 0 0.0
4 0 0.0
# 计数
>df.groupby().count()
好坏客户 家属数量
0 136229
1 9847
# 求和
>df.groupby().sum()
好坏客户 家属数量
0 101275.0
1 9337.0
按照多列进行分组
以下面的数据表为例:
好坏客户 月收入 家属数量
0 1 9120.0 2.0
1 0 2600.0 1.0
2 0 3042.0 0.0
...
dfGroup = df.groupby(by=["好坏客户", "家属数量"])
print(dfGroup.count())
print(dfGroup.sum())
count():
好坏客户 家属数量 月收入
0 0.0 61562
1.0 22561
2.0 16624
...
20.0 1
1 0.0 3894
1.0 1818
2.0 1486
...
8.0 2
sum():
好坏客户 家属数量 月收入
0 0.0 364452974.0
1.0 165071163.0
2.0 128145556.0
...
20.0 6316.0
1 0.0 19997047.0
1.0 10583734.0
2.0 9058764.0
...
8.0 7868.0
aggregate()方法
aggregate()的特点如下:
- 可以一次使用多种汇总方式
- 可以针对不同的列做不同的汇总运算
以下面的数据表为例:
用户ID 客户分类 区域 是否省会 7月销量 8月销量 9月销量
0 59224 A类 一线城市 是 6 20 0
1 55295 B类 三线城市 否 37 27 35
2 46035 A类 二线城市 是 8 1 8
3 2459 C类 一线城市 是 7 8 14
4 22179 B类 三线城市 否 9 12 4
5 22557 A类 二线城市 是 42 20 55
# 对用户ID做计数运算,销量做求和运算
df.groupby(by='客户分类').aggregate({
'用户ID': 'count',
'7月销量': 'sum',
'8月销量': 'sum',
'9月销量': 'sum'
})
用户ID 7月销量 8月销量 9月销量
客户分类
A类 3 56 41 63
B类 2 46 39 39
C类 1 7 8 14
数据透视表
数据分组是在一维(行)方向上不断拆分,而数据透视表是在行、列方向上同时拆分。
pandas中实现数据透视表常用pivot_table()方法,结合实例如下:
用户ID 客户分类 区域 是否省会 7月销量 8月销量 9月销量
0 59224 A类 一线城市 是 6 20 0
1 55295 B类 三线城市 否 37 27 35
2 46035 A类 二线城市 是 8 1 8
3 2459 C类 一线城市 是 7 8 14
4 22179 B类 三线城市 否 9 12 4
5 22557 A类 二线城市 是 42 20 55
pd.pivot_table(
data=df,
values=['用户ID', '7月销量'],
columns='区域',
index='客户分类',
aggfunc={'用户ID': 'count', '7月销量': 'sum'},
margins=True,
margins_name='总计',
fill_value=0
)
7月销量 用户ID
区域 一线城市 三线城市 二线城市 总计 一线城市 三线城市 二线城市 总计
客户分类
A类 6 0 50 56 1 0 2 3
B类 0 46 0 46 0 2 0 2
C类 7 0 0 7 1 0 0 1
总计 13 46 50 109 2 2 2 6
参数解释如下:
- data传入数据表
- values指定值,对应Excel中值那个框
- columns指定列索引,对应Excel中的列
- index指定行索引,对应Excel中的行
- aggfunc用于指定对values的计算类型(count | sum),可传入字典做不同运算
- margins指定是否显示合计列(all),默认不显示
- margins_name用于修改合计列的名称
- fill_value指定空值的填充值